Compare commits
No commits in common. "main" and "v.2.9.1-WP14_WP19b" have entirely different histories.
main
...
v.2.9.1-WP
|
|
@ -1,237 +0,0 @@
|
|||
# Analyse: Zugriffe auf config/types.yaml
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
Diese Analyse prüft, welche Scripte auf `config/types.yaml` zugreifen und ob sie auf Elemente zugreifen, die in der aktuellen `types.yaml` nicht mehr vorhanden sind.
|
||||
|
||||
**Datum:** 2025-01-XX
|
||||
**Version types.yaml:** 2.7.0
|
||||
|
||||
---
|
||||
|
||||
## ❌ KRITISCHE PROBLEME
|
||||
|
||||
### 1. `edge_defaults` fehlt in types.yaml, wird aber im Code verwendet
|
||||
|
||||
**Status:** ⚠️ **PROBLEM** - Code sucht nach `edge_defaults` in types.yaml, aber dieses Feld existiert nicht mehr.
|
||||
|
||||
**Betroffene Dateien:**
|
||||
|
||||
#### a) `app/core/graph/graph_utils.py` (Zeilen 101-112)
|
||||
```python
|
||||
def get_edge_defaults_for(note_type: Optional[str], reg: dict) -> List[str]:
|
||||
"""Ermittelt Standard-Kanten für einen Typ."""
|
||||
types_map = reg.get("types", reg) if isinstance(reg, dict) else {}
|
||||
if note_type and isinstance(types_map, dict):
|
||||
t = types_map.get(note_type)
|
||||
if isinstance(t, dict) and isinstance(t.get("edge_defaults"), list): # ❌ Sucht nach edge_defaults
|
||||
return [str(x) for x in t["edge_defaults"] if isinstance(x, str)]
|
||||
for key in ("defaults", "default", "global"):
|
||||
v = reg.get(key)
|
||||
if isinstance(v, dict) and isinstance(v.get("edge_defaults"), list): # ❌ Sucht nach edge_defaults
|
||||
return [str(x) for x in v["edge_defaults"] if isinstance(x, str)]
|
||||
return []
|
||||
```
|
||||
**Problem:** Funktion gibt immer `[]` zurück, da `edge_defaults` nicht in types.yaml existiert.
|
||||
|
||||
#### b) `app/core/graph/graph_derive_edges.py` (Zeile 64)
|
||||
```python
|
||||
defaults = get_edge_defaults_for(note_type, reg) # ❌ Wird verwendet, liefert aber []
|
||||
```
|
||||
**Problem:** Keine automatischen Default-Kanten werden mehr erzeugt.
|
||||
|
||||
#### c) `app/services/discovery.py` (Zeile 212)
|
||||
```python
|
||||
defaults = type_def.get("edge_defaults") # ❌ Sucht nach edge_defaults
|
||||
return defaults[0] if defaults else "related_to"
|
||||
```
|
||||
**Problem:** Fallback funktioniert, aber nutzt nicht die neue dynamische Lösung.
|
||||
|
||||
#### d) `tests/check_types_registry_edges.py` (Zeile 170)
|
||||
```python
|
||||
eddefs = (tdef or {}).get("edge_defaults") or [] # ❌ Sucht nach edge_defaults
|
||||
```
|
||||
**Problem:** Test findet keine `edge_defaults` mehr und gibt Warnung aus.
|
||||
|
||||
**✅ Lösung bereits implementiert:**
|
||||
- `app/core/ingestion/ingestion_note_payload.py` (WP-24c, Zeilen 124-134) nutzt bereits die neue dynamische Lösung über `edge_registry.get_topology_info()`.
|
||||
|
||||
**Empfehlung:**
|
||||
- `get_edge_defaults_for()` in `graph_utils.py` sollte auf die EdgeRegistry umgestellt werden.
|
||||
- `discovery.py` sollte ebenfalls die EdgeRegistry nutzen.
|
||||
|
||||
---
|
||||
|
||||
### 2. Inkonsistenz: `chunk_profile` vs `chunking_profile`
|
||||
|
||||
**Status:** ⚠️ **WARNUNG** - Meistens abgefangen durch Fallback-Logik.
|
||||
|
||||
**Problem:**
|
||||
- In `types.yaml` heißt es: `chunking_profile` ✅
|
||||
- `app/core/type_registry.py` (Zeile 88) sucht nach: `chunk_profile` ❌
|
||||
|
||||
```python
|
||||
def effective_chunk_profile(note_type: Optional[str], reg: Dict[str, Any]) -> Optional[str]:
|
||||
cfg = get_type_config(note_type, reg)
|
||||
prof = cfg.get("chunk_profile") # ❌ Sucht nach "chunk_profile", aber types.yaml hat "chunking_profile"
|
||||
if isinstance(prof, str) and prof.strip():
|
||||
return prof.strip().lower()
|
||||
return None
|
||||
```
|
||||
|
||||
**Betroffene Dateien:**
|
||||
- `app/core/type_registry.py` (Zeile 88) - verwendet `chunk_profile` statt `chunking_profile`
|
||||
|
||||
**✅ Gut gehandhabt:**
|
||||
- `app/core/ingestion/ingestion_chunk_payload.py` (Zeile 33) - hat Fallback: `t_cfg.get(key) or t_cfg.get(key.replace("ing", ""))`
|
||||
- `app/core/ingestion/ingestion_note_payload.py` (Zeile 120) - prüft beide Varianten
|
||||
|
||||
**Empfehlung:**
|
||||
- `type_registry.py` sollte auch `chunking_profile` prüfen (oder beide Varianten).
|
||||
|
||||
---
|
||||
|
||||
## ✅ KORREKT VERWENDETE ELEMENTE
|
||||
|
||||
### 1. `chunking_profiles` ✅
|
||||
- **Verwendet in:**
|
||||
- `app/core/chunking/chunking_utils.py` (Zeile 33) ✅
|
||||
- **Status:** Korrekt vorhanden in types.yaml
|
||||
|
||||
### 2. `defaults` ✅
|
||||
- **Verwendet in:**
|
||||
- `app/core/ingestion/ingestion_chunk_payload.py` (Zeile 36) ✅
|
||||
- `app/core/ingestion/ingestion_note_payload.py` (Zeile 104) ✅
|
||||
- `app/core/chunking/chunking_utils.py` (Zeile 35) ✅
|
||||
- **Status:** Korrekt vorhanden in types.yaml
|
||||
|
||||
### 3. `ingestion_settings` ✅
|
||||
- **Verwendet in:**
|
||||
- `app/core/ingestion/ingestion_note_payload.py` (Zeile 105) ✅
|
||||
- **Status:** Korrekt vorhanden in types.yaml
|
||||
|
||||
### 4. `llm_settings` ✅
|
||||
- **Verwendet in:**
|
||||
- `app/core/registry.py` (Zeile 37) ✅
|
||||
- **Status:** Korrekt vorhanden in types.yaml
|
||||
|
||||
### 5. `types` (Hauptstruktur) ✅
|
||||
- **Verwendet in:** Viele Dateien
|
||||
- **Status:** Korrekt vorhanden in types.yaml
|
||||
|
||||
### 6. `types[].chunking_profile` ✅
|
||||
- **Verwendet in:**
|
||||
- `app/core/chunking/chunking_utils.py` (Zeile 35) ✅
|
||||
- `app/core/ingestion/ingestion_chunk_payload.py` (Zeile 67) ✅
|
||||
- `app/core/ingestion/ingestion_note_payload.py` (Zeile 120) ✅
|
||||
- **Status:** Korrekt vorhanden in types.yaml
|
||||
|
||||
### 7. `types[].retriever_weight` ✅
|
||||
- **Verwendet in:**
|
||||
- `app/core/ingestion/ingestion_chunk_payload.py` (Zeile 71) ✅
|
||||
- `app/core/ingestion/ingestion_note_payload.py` (Zeile 111) ✅
|
||||
- `app/core/retrieval/retriever_scoring.py` (Zeile 87) ✅
|
||||
- **Status:** Korrekt vorhanden in types.yaml
|
||||
|
||||
### 8. `types[].detection_keywords` ✅
|
||||
- **Verwendet in:**
|
||||
- `app/routers/chat.py` (Zeilen 104, 150) ✅
|
||||
- **Status:** Korrekt vorhanden in types.yaml
|
||||
|
||||
### 9. `types[].schema` ✅
|
||||
- **Verwendet in:**
|
||||
- `app/routers/chat.py` (vermutlich) ✅
|
||||
- **Status:** Korrekt vorhanden in types.yaml
|
||||
|
||||
---
|
||||
|
||||
## 📋 ZUSAMMENFASSUNG DER ZUGRIFFE
|
||||
|
||||
### Dateien, die auf types.yaml zugreifen:
|
||||
|
||||
1. **app/core/type_registry.py** ⚠️
|
||||
- Verwendet: `types`, `chunk_profile` (sollte `chunking_profile` sein)
|
||||
- Problem: Sucht nach `chunk_profile` statt `chunking_profile`
|
||||
|
||||
2. **app/core/registry.py** ✅
|
||||
- Verwendet: `llm_settings.cleanup_patterns`
|
||||
- Status: OK
|
||||
|
||||
3. **app/core/ingestion/ingestion_chunk_payload.py** ✅
|
||||
- Verwendet: `types`, `defaults`, `chunking_profile`, `retriever_weight`
|
||||
- Status: OK (hat Fallback für chunk_profile/chunking_profile)
|
||||
|
||||
4. **app/core/ingestion/ingestion_note_payload.py** ✅
|
||||
- Verwendet: `types`, `defaults`, `ingestion_settings`, `chunking_profile`, `retriever_weight`
|
||||
- Status: OK (nutzt neue EdgeRegistry für edge_defaults)
|
||||
|
||||
5. **app/core/chunking/chunking_utils.py** ✅
|
||||
- Verwendet: `chunking_profiles`, `types`, `defaults.chunking_profile`
|
||||
- Status: OK
|
||||
|
||||
6. **app/core/retrieval/retriever_scoring.py** ✅
|
||||
- Verwendet: `retriever_weight` (aus Payload, kommt ursprünglich aus types.yaml)
|
||||
- Status: OK
|
||||
|
||||
7. **app/core/graph/graph_utils.py** ❌
|
||||
- Verwendet: `types[].edge_defaults` (existiert nicht mehr!)
|
||||
- Problem: Sucht nach `edge_defaults` in types.yaml
|
||||
|
||||
8. **app/core/graph/graph_derive_edges.py** ❌
|
||||
- Verwendet: `get_edge_defaults_for()` → sucht nach `edge_defaults`
|
||||
- Problem: Keine Default-Kanten mehr
|
||||
|
||||
9. **app/services/discovery.py** ⚠️
|
||||
- Verwendet: `types[].edge_defaults` (existiert nicht mehr!)
|
||||
- Problem: Fallback funktioniert, aber nutzt nicht neue Lösung
|
||||
|
||||
10. **app/routers/chat.py** ✅
|
||||
- Verwendet: `types[].detection_keywords`
|
||||
- Status: OK
|
||||
|
||||
11. **tests/test_type_registry.py** ⚠️
|
||||
- Verwendet: `types[].chunk_profile`, `types[].edge_defaults`
|
||||
- Problem: Test verwendet alte Struktur
|
||||
|
||||
12. **tests/check_types_registry_edges.py** ❌
|
||||
- Verwendet: `types[].edge_defaults` (existiert nicht mehr!)
|
||||
- Problem: Test findet keine edge_defaults
|
||||
|
||||
13. **scripts/payload_dryrun.py** ✅
|
||||
- Verwendet: Indirekt über `make_note_payload()` und `make_chunk_payloads()`
|
||||
- Status: OK
|
||||
|
||||
---
|
||||
|
||||
## 🔧 EMPFOHLENE FIXES
|
||||
|
||||
### Priorität 1 (Kritisch):
|
||||
|
||||
1. **`app/core/graph/graph_utils.py` - `get_edge_defaults_for()`**
|
||||
- Sollte auf `edge_registry.get_topology_info()` umgestellt werden
|
||||
- Oder: Rückwärtskompatibilität beibehalten, aber EdgeRegistry als primäre Quelle nutzen
|
||||
|
||||
2. **`app/core/graph/graph_derive_edges.py`**
|
||||
- Nutzt `get_edge_defaults_for()`, sollte nach Fix von graph_utils.py funktionieren
|
||||
|
||||
3. **`app/services/discovery.py`**
|
||||
- Sollte EdgeRegistry für `edge_defaults` nutzen
|
||||
|
||||
### Priorität 2 (Warnung):
|
||||
|
||||
4. **`app/core/type_registry.py` - `effective_chunk_profile()`**
|
||||
- Sollte auch `chunking_profile` prüfen (nicht nur `chunk_profile`)
|
||||
|
||||
5. **`tests/test_type_registry.py`**
|
||||
- Test sollte aktualisiert werden, um `chunking_profile` statt `chunk_profile` zu verwenden
|
||||
|
||||
6. **`tests/check_types_registry_edges.py`**
|
||||
- Test sollte auf EdgeRegistry umgestellt werden oder als deprecated markiert werden
|
||||
|
||||
---
|
||||
|
||||
## 📝 HINWEISE
|
||||
|
||||
- **WP-24c** hat bereits eine Lösung für `edge_defaults` implementiert: Dynamische Abfrage über `edge_registry.get_topology_info()`
|
||||
- Die alte Lösung (statische `edge_defaults` in types.yaml) wurde durch die dynamische Lösung ersetzt
|
||||
- Code-Stellen, die noch die alte Lösung verwenden, sollten migriert werden
|
||||
|
|
@ -15,44 +15,13 @@ from dotenv import load_dotenv
|
|||
|
||||
# WP-20: Lade Umgebungsvariablen aus der .env Datei
|
||||
# override=True garantiert, dass Änderungen in der .env immer Vorrang haben.
|
||||
# WP-24c v4.5.10: Expliziter Pfad für .env-Datei, um Probleme mit Arbeitsverzeichnis zu vermeiden
|
||||
# Suche .env im Projekt-Root (3 Ebenen über app/config.py: app/config.py -> app/ -> root/)
|
||||
_project_root = Path(__file__).parent.parent.parent
|
||||
_env_file = _project_root / ".env"
|
||||
_env_loaded = False
|
||||
|
||||
# Versuche zuerst expliziten Pfad
|
||||
if _env_file.exists():
|
||||
_env_loaded = load_dotenv(_env_file, override=True)
|
||||
if _env_loaded:
|
||||
# Optional: Logging (nur wenn logging bereits initialisiert ist)
|
||||
try:
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
_logger.debug(f"✅ .env geladen von: {_env_file}")
|
||||
except:
|
||||
pass # Logging noch nicht initialisiert
|
||||
|
||||
# Fallback: Automatische Suche (für Dev/Test oder wenn .env an anderer Stelle liegt)
|
||||
if not _env_loaded:
|
||||
_env_loaded = load_dotenv(override=True)
|
||||
if _env_loaded:
|
||||
try:
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
_logger.debug(f"✅ .env geladen via automatische Suche (cwd: {Path.cwd()})")
|
||||
except:
|
||||
pass
|
||||
load_dotenv(override=True)
|
||||
|
||||
class Settings:
|
||||
# --- Qdrant Datenbank ---
|
||||
QDRANT_URL: str = os.getenv("QDRANT_URL", "http://127.0.0.1:6333")
|
||||
QDRANT_API_KEY: str | None = os.getenv("QDRANT_API_KEY")
|
||||
# WP-24c v4.5.10: Harmonisierung - Unterstützt beide Umgebungsvariablen für Abwärtskompatibilität
|
||||
# COLLECTION_PREFIX hat Priorität, MINDNET_PREFIX als Fallback
|
||||
# WP-24c v4.5.10-FIX: Default auf "mindnet" (Prod) statt "mindnet_dev" (Dev)
|
||||
# Dev muss explizit COLLECTION_PREFIX=mindnet_dev in .env setzen
|
||||
COLLECTION_PREFIX: str = os.getenv("COLLECTION_PREFIX") or os.getenv("MINDNET_PREFIX") or "mindnet"
|
||||
COLLECTION_PREFIX: str = os.getenv("MINDNET_PREFIX", "mindnet_dev")
|
||||
|
||||
# WP-22: Vektor-Dimension für das Embedding-Modell (nomic)
|
||||
VECTOR_SIZE: int = int(os.getenv("VECTOR_DIM", "768"))
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ class RawBlock:
|
|||
level: Optional[int]
|
||||
section_path: str
|
||||
section_title: Optional[str]
|
||||
exclude_from_chunking: bool = False # WP-24c v4.2.0: Flag für Edge-Zonen, die nicht gechunkt werden sollen
|
||||
is_meta_content: bool = False # WP-24c v4.2.6: Flag für Meta-Content (Callouts), der später entfernt wird
|
||||
|
||||
@dataclass
|
||||
class Chunk:
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
"""
|
||||
FILE: app/core/chunking/chunking_parser.py
|
||||
DESCRIPTION: Zerlegt Markdown in logische Einheiten (RawBlocks).
|
||||
Hält alle Überschriftenebenen (H1-H6) im Stream.
|
||||
Stellt die Funktion parse_edges_robust zur Verfügung.
|
||||
WP-24c v4.2.0: Identifiziert Edge-Zonen und markiert sie für Chunking-Ausschluss.
|
||||
WP-24c v4.2.5: Callout-Exclusion - Callouts werden als separate RawBlocks identifiziert und ausgeschlossen.
|
||||
DESCRIPTION: Zerlegt Markdown in Blöcke und extrahiert Kanten-Strings.
|
||||
"""
|
||||
import re
|
||||
import os
|
||||
from typing import List, Tuple, Set, Dict, Any, Optional
|
||||
from typing import List, Tuple, Set
|
||||
from .chunking_models import RawBlock
|
||||
from .chunking_utils import extract_frontmatter_from_text
|
||||
|
||||
|
|
@ -16,236 +11,83 @@ _WS = re.compile(r'\s+')
|
|||
_SENT_SPLIT = re.compile(r'(?<=[.!?])\s+(?=[A-ZÄÖÜ0-9„(])')
|
||||
|
||||
def split_sentences(text: str) -> list[str]:
|
||||
"""Teilt Text in Sätze auf unter Berücksichtigung deutscher Interpunktion."""
|
||||
"""Teilt Text in Sätze auf."""
|
||||
text = _WS.sub(' ', text.strip())
|
||||
if not text: return []
|
||||
# Splittet bei Punkt, Ausrufezeichen oder Fragezeichen, gefolgt von Leerzeichen und Großbuchstabe
|
||||
return [p.strip() for p in _SENT_SPLIT.split(text) if p.strip()]
|
||||
|
||||
def parse_blocks(md_text: str) -> Tuple[List[RawBlock], str]:
|
||||
"""
|
||||
Zerlegt Text in logische Einheiten (RawBlocks), inklusive H1-H6.
|
||||
WP-24c v4.2.0: Identifiziert Edge-Zonen (LLM-Validierung & Note-Scope) und markiert sie für Chunking-Ausschluss.
|
||||
WP-24c v4.2.6: Callouts werden mit is_meta_content=True markiert (werden gechunkt, aber später entfernt).
|
||||
"""
|
||||
"""Zerlegt Text in logische Einheiten."""
|
||||
blocks = []
|
||||
h1_title = "Dokument"
|
||||
section_path = "/"
|
||||
current_section_title = None
|
||||
|
||||
# Frontmatter entfernen
|
||||
h1_title = "Dokument"; section_path = "/"; current_h2 = None
|
||||
fm, text_without_fm = extract_frontmatter_from_text(md_text)
|
||||
|
||||
# WP-24c v4.2.0: Konfigurierbare Header-Namen und -Ebenen
|
||||
llm_validation_headers = os.getenv(
|
||||
"MINDNET_LLM_VALIDATION_HEADERS",
|
||||
"Unzugeordnete Kanten,Edge Pool,Candidates"
|
||||
)
|
||||
llm_validation_header_list = [h.strip() for h in llm_validation_headers.split(",") if h.strip()]
|
||||
if not llm_validation_header_list:
|
||||
llm_validation_header_list = ["Unzugeordnete Kanten", "Edge Pool", "Candidates"]
|
||||
|
||||
note_scope_headers = os.getenv(
|
||||
"MINDNET_NOTE_SCOPE_ZONE_HEADERS",
|
||||
"Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen"
|
||||
)
|
||||
note_scope_header_list = [h.strip() for h in note_scope_headers.split(",") if h.strip()]
|
||||
if not note_scope_header_list:
|
||||
note_scope_header_list = ["Smart Edges", "Relationen", "Global Links", "Note-Level Relations", "Globale Verbindungen"]
|
||||
|
||||
# Header-Ebenen konfigurierbar (Default: LLM=3, Note-Scope=2)
|
||||
llm_validation_level = int(os.getenv("MINDNET_LLM_VALIDATION_HEADER_LEVEL", "3"))
|
||||
note_scope_level = int(os.getenv("MINDNET_NOTE_SCOPE_HEADER_LEVEL", "2"))
|
||||
|
||||
# Status-Tracking für Edge-Zonen
|
||||
in_exclusion_zone = False
|
||||
exclusion_zone_type = None # "llm_validation" oder "note_scope"
|
||||
|
||||
# H1 für Note-Titel extrahieren (Metadaten-Zweck)
|
||||
h1_match = re.search(r'^#\s+(.*)', text_without_fm, re.MULTILINE)
|
||||
if h1_match:
|
||||
h1_title = h1_match.group(1).strip()
|
||||
|
||||
if h1_match: h1_title = h1_match.group(1).strip()
|
||||
lines = text_without_fm.split('\n')
|
||||
buffer = []
|
||||
|
||||
# WP-24c v4.2.5: Callout-Erkennung (auch verschachtelt: >>)
|
||||
# Regex für Callouts: >\s*[!edge] oder >\s*[!abstract] (auch mit mehreren >)
|
||||
callout_pattern = re.compile(r'^\s*>{1,}\s*\[!(edge|abstract)\]', re.IGNORECASE)
|
||||
|
||||
# WP-24c v4.2.5: Markiere verarbeitete Zeilen, um sie zu überspringen
|
||||
processed_indices = set()
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if i in processed_indices:
|
||||
continue
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
|
||||
# WP-24c v4.2.5: Callout-Erkennung (VOR Heading-Erkennung)
|
||||
# Prüfe, ob diese Zeile ein Callout startet
|
||||
callout_match = callout_pattern.match(line)
|
||||
if callout_match:
|
||||
# Vorherigen Text-Block abschließen
|
||||
if buffer:
|
||||
content = "\n".join(buffer).strip()
|
||||
if content:
|
||||
blocks.append(RawBlock(
|
||||
"paragraph", content, None, section_path, current_section_title,
|
||||
exclude_from_chunking=in_exclusion_zone
|
||||
))
|
||||
buffer = []
|
||||
|
||||
# Sammle alle Zeilen des Callout-Blocks
|
||||
callout_lines = [line]
|
||||
leading_gt_count = len(line) - len(line.lstrip('>'))
|
||||
processed_indices.add(i)
|
||||
|
||||
# Sammle alle Zeilen, die zum Callout gehören (gleiche oder höhere Einrückung)
|
||||
j = i + 1
|
||||
while j < len(lines):
|
||||
next_line = lines[j]
|
||||
if not next_line.strip().startswith('>'):
|
||||
break
|
||||
next_leading_gt = len(next_line) - len(next_line.lstrip('>'))
|
||||
if next_leading_gt < leading_gt_count:
|
||||
break
|
||||
callout_lines.append(next_line)
|
||||
processed_indices.add(j)
|
||||
j += 1
|
||||
|
||||
# WP-24c v4.2.6: Erstelle Callout-Block mit is_meta_content = True
|
||||
# Callouts werden gechunkt (für Chunk-Attribution), aber später entfernt (Clean-Context)
|
||||
callout_content = "\n".join(callout_lines)
|
||||
blocks.append(RawBlock(
|
||||
"callout", callout_content, None, section_path, current_section_title,
|
||||
exclude_from_chunking=in_exclusion_zone, # Nur Edge-Zonen werden ausgeschlossen
|
||||
is_meta_content=True # WP-24c v4.2.6: Markierung für spätere Entfernung
|
||||
))
|
||||
# H1 ignorieren (ist Doc Title)
|
||||
if stripped.startswith('# '):
|
||||
continue
|
||||
|
||||
# Heading-Erkennung (H1 bis H6)
|
||||
heading_match = re.match(r'^(#{1,6})\s+(.*)', stripped)
|
||||
# Generische Heading-Erkennung (H2 bis H6) für flexible Split-Levels
|
||||
heading_match = re.match(r'^(#{2,6})\s+(.*)', stripped)
|
||||
if heading_match:
|
||||
# Vorherigen Text-Block abschließen
|
||||
# Buffer leeren (vorherigen Text abschließen)
|
||||
if buffer:
|
||||
content = "\n".join(buffer).strip()
|
||||
if content:
|
||||
blocks.append(RawBlock(
|
||||
"paragraph", content, None, section_path, current_section_title,
|
||||
exclude_from_chunking=in_exclusion_zone
|
||||
))
|
||||
if content: blocks.append(RawBlock("paragraph", content, None, section_path, current_h2))
|
||||
buffer = []
|
||||
|
||||
level = len(heading_match.group(1))
|
||||
title = heading_match.group(2).strip()
|
||||
|
||||
# WP-24c v4.2.0: Prüfe, ob dieser Header eine Edge-Zone startet
|
||||
is_llm_validation_zone = (
|
||||
level == llm_validation_level and
|
||||
any(title.lower() == h.lower() for h in llm_validation_header_list)
|
||||
)
|
||||
is_note_scope_zone = (
|
||||
level == note_scope_level and
|
||||
any(title.lower() == h.lower() for h in note_scope_header_list)
|
||||
)
|
||||
# Pfad-Logik: H2 setzt den Haupt-Pfad
|
||||
if level == 2:
|
||||
current_h2 = title
|
||||
section_path = f"/{current_h2}"
|
||||
# Bei H3+ bleibt der section_path beim Parent, aber das Level wird korrekt gesetzt
|
||||
|
||||
if is_llm_validation_zone:
|
||||
in_exclusion_zone = True
|
||||
exclusion_zone_type = "llm_validation"
|
||||
elif is_note_scope_zone:
|
||||
in_exclusion_zone = True
|
||||
exclusion_zone_type = "note_scope"
|
||||
elif in_exclusion_zone:
|
||||
# Neuer Header gefunden, der keine Edge-Zone ist -> Zone beendet
|
||||
in_exclusion_zone = False
|
||||
exclusion_zone_type = None
|
||||
blocks.append(RawBlock("heading", stripped, level, section_path, current_h2))
|
||||
|
||||
# Pfad- und Titel-Update für die Metadaten der folgenden Blöcke
|
||||
if level == 1:
|
||||
current_section_title = title; section_path = "/"
|
||||
elif level == 2:
|
||||
current_section_title = title; section_path = f"/{current_section_title}"
|
||||
|
||||
# Die Überschrift selbst als regulären Block hinzufügen (auch markiert, wenn in Zone)
|
||||
blocks.append(RawBlock(
|
||||
"heading", stripped, level, section_path, current_section_title,
|
||||
exclude_from_chunking=in_exclusion_zone
|
||||
))
|
||||
continue
|
||||
|
||||
# Trenner (---) oder Leerzeilen beenden Blöcke, außer innerhalb von Callouts
|
||||
if (not stripped or stripped == "---") and not line.startswith('>'):
|
||||
elif not stripped:
|
||||
if buffer:
|
||||
content = "\n".join(buffer).strip()
|
||||
if content:
|
||||
blocks.append(RawBlock(
|
||||
"paragraph", content, None, section_path, current_section_title,
|
||||
exclude_from_chunking=in_exclusion_zone
|
||||
))
|
||||
if content: blocks.append(RawBlock("paragraph", content, None, section_path, current_h2))
|
||||
buffer = []
|
||||
if stripped == "---":
|
||||
blocks.append(RawBlock(
|
||||
"separator", "---", None, section_path, current_section_title,
|
||||
exclude_from_chunking=in_exclusion_zone
|
||||
))
|
||||
else:
|
||||
buffer.append(line)
|
||||
|
||||
if buffer:
|
||||
content = "\n".join(buffer).strip()
|
||||
if content:
|
||||
blocks.append(RawBlock(
|
||||
"paragraph", content, None, section_path, current_section_title,
|
||||
exclude_from_chunking=in_exclusion_zone
|
||||
))
|
||||
|
||||
if content: blocks.append(RawBlock("paragraph", content, None, section_path, current_h2))
|
||||
return blocks, h1_title
|
||||
|
||||
def parse_edges_robust(text: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Extrahiert Kanten-Kandidaten aus Wikilinks und Callouts.
|
||||
WP-24c v4.2.7: Gibt Liste von Dicts zurück mit is_callout Flag für Chunk-Attribution.
|
||||
WP-24c v4.2.9 Fix A: current_edge_type bleibt über Leerzeilen hinweg erhalten,
|
||||
damit alle Links in einem Callout-Block korrekt verarbeitet werden.
|
||||
|
||||
Returns:
|
||||
List[Dict] mit keys: "edge" (str: "kind:target"), "is_callout" (bool)
|
||||
"""
|
||||
found_edges: List[Dict[str, any]] = []
|
||||
# 1. Wikilinks [[rel:kind|target]]
|
||||
def parse_edges_robust(text: str) -> Set[str]:
|
||||
"""Extrahiert Kanten-Kandidaten (Wikilinks, Callouts)."""
|
||||
found_edges = set()
|
||||
inlines = re.findall(r'\[\[rel:([^\|\]]+)\|?([^\]]*)\]\]', text)
|
||||
for kind, target in inlines:
|
||||
k = kind.strip().lower()
|
||||
t = target.strip()
|
||||
if k and t:
|
||||
found_edges.append({"edge": f"{k}:{t}", "is_callout": False})
|
||||
|
||||
# 2. Callout Edges > [!edge] kind
|
||||
if k and t: found_edges.add(f"{k}:{t}")
|
||||
lines = text.split('\n')
|
||||
current_edge_type = None
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
callout_match = re.match(r'>+\s*\[!edge\]\s*([^:\s]+)', stripped)
|
||||
callout_match = re.match(r'>\s*\[!edge\]\s*([^:\s]+)', stripped)
|
||||
if callout_match:
|
||||
current_edge_type = callout_match.group(1).strip().lower()
|
||||
# Links in der gleichen Zeile des Callouts
|
||||
links = re.findall(r'\[\[([^\]]+)\]\]', stripped)
|
||||
for l in links:
|
||||
if "rel:" not in l:
|
||||
found_edges.append({"edge": f"{current_edge_type}:{l}", "is_callout": True})
|
||||
if "rel:" not in l: found_edges.add(f"{current_edge_type}:{l}")
|
||||
continue
|
||||
# Links in Folgezeilen des Callouts
|
||||
# WP-24c v4.2.9 Fix A: current_edge_type bleibt über Leerzeilen hinweg erhalten
|
||||
# innerhalb eines Callout-Blocks, damit alle Links korrekt verarbeitet werden
|
||||
if current_edge_type and stripped.startswith('>'):
|
||||
# Fortsetzung des Callout-Blocks: Links extrahieren
|
||||
links = re.findall(r'\[\[([^\]]+)\]\]', stripped)
|
||||
for l in links:
|
||||
if "rel:" not in l:
|
||||
found_edges.append({"edge": f"{current_edge_type}:{l}", "is_callout": True})
|
||||
elif current_edge_type and not stripped.startswith('>') and stripped:
|
||||
# Nicht-Callout-Zeile mit Inhalt: Callout-Block beendet
|
||||
current_edge_type = None
|
||||
# Leerzeilen werden ignoriert - current_edge_type bleibt erhalten
|
||||
if "rel:" not in l: found_edges.add(f"{current_edge_type}:{l}")
|
||||
elif not stripped.startswith('>'): current_edge_type = None
|
||||
return found_edges
|
||||
|
|
@ -1,26 +1,13 @@
|
|||
"""
|
||||
FILE: app/core/chunking/chunking_processor.py
|
||||
DESCRIPTION: Der zentrale Orchestrator für das Chunking-System.
|
||||
AUDIT v3.3.4: Wiederherstellung der "Gold-Standard" Qualität.
|
||||
- Fix: Synchronisierung der Parameter (context_prefix) für alle Strategien.
|
||||
AUDIT v3.3.3: Wiederherstellung der "Gold-Standard" Qualität.
|
||||
- Integriert physikalische Kanten-Injektion (Propagierung).
|
||||
- Stellt H1-Kontext-Fenster sicher.
|
||||
- Baut den Candidate-Pool für die WP-15b Ingestion auf.
|
||||
WP-24c v4.2.0: Konfigurierbare Header-Namen für LLM-Validierung.
|
||||
WP-24c v4.2.5: Wiederherstellung der Chunking-Präzision
|
||||
- Frontmatter-Override für chunking_profile
|
||||
- Callout-Exclusion aus Chunks
|
||||
- Strict-Mode ohne Carry-Over
|
||||
WP-24c v4.2.6: Finale Härtung - "Semantic First, Clean Second"
|
||||
- Callouts werden gechunkt (Chunk-Attribution), aber später entfernt (Clean-Context)
|
||||
- remove_callouts_from_text erst nach propagate_section_edges und Candidate Pool
|
||||
WP-24c v4.2.7: Wiederherstellung der Chunk-Attribution
|
||||
- Callout-Kanten erhalten explicit:callout Provenance im candidate_pool
|
||||
- graph_derive_edges.py erkennt diese und verhindert Note-Scope Duplikate
|
||||
"""
|
||||
import asyncio
|
||||
import re
|
||||
import os
|
||||
import logging
|
||||
from typing import List, Dict, Optional
|
||||
from .chunking_models import Chunk
|
||||
|
|
@ -35,167 +22,70 @@ async def assemble_chunks(note_id: str, md_text: str, note_type: str, config: Op
|
|||
"""
|
||||
Hauptfunktion zur Zerlegung einer Note.
|
||||
Verbindet Strategien mit physikalischer Kontext-Anreicherung.
|
||||
WP-24c v4.2.5: Frontmatter-Override für chunking_profile wird berücksichtigt.
|
||||
"""
|
||||
# 1. WP-24c v4.2.5: Frontmatter VOR Konfiguration extrahieren (für Override)
|
||||
fm, body_text = extract_frontmatter_from_text(md_text)
|
||||
|
||||
# 2. Konfiguration mit Frontmatter-Override
|
||||
# 1. Konfiguration & Parsing
|
||||
if config is None:
|
||||
config = get_chunk_config(note_type, frontmatter=fm)
|
||||
config = get_chunk_config(note_type)
|
||||
|
||||
fm, body_text = extract_frontmatter_from_text(md_text)
|
||||
blocks, doc_title = parse_blocks(md_text)
|
||||
|
||||
# WP-24c v4.2.6: Filtere NUR Edge-Zonen (LLM-Validierung & Note-Scope)
|
||||
# Callouts (is_meta_content=True) müssen durch, damit Chunk-Attribution erhalten bleibt
|
||||
blocks_for_chunking = [b for b in blocks if not getattr(b, 'exclude_from_chunking', False)]
|
||||
|
||||
# Vorbereitung des H1-Präfix für die Embedding-Fenster (Breadcrumbs)
|
||||
# Vorbereitung des H1-Präfix für die Embedding-Fenster
|
||||
h1_prefix = f"# {doc_title}" if doc_title else ""
|
||||
|
||||
# 2. Anwendung der Splitting-Strategie
|
||||
# Alle Strategien nutzen nun einheitlich context_prefix für die Window-Bildung.
|
||||
# WP-24c v4.2.6: Callouts sind in blocks_for_chunking enthalten (für Chunk-Attribution)
|
||||
# Wir übergeben den Dokument-Titel/Präfix für die Window-Bildung.
|
||||
if config.get("strategy") == "by_heading":
|
||||
chunks = await asyncio.to_thread(
|
||||
strategy_by_heading, blocks_for_chunking, config, note_id, context_prefix=h1_prefix
|
||||
)
|
||||
chunks = await asyncio.to_thread(strategy_by_heading, blocks, config, note_id, doc_title)
|
||||
else:
|
||||
chunks = await asyncio.to_thread(
|
||||
strategy_sliding_window, blocks_for_chunking, config, note_id, context_prefix=h1_prefix
|
||||
)
|
||||
# sliding_window nutzt nun den context_prefix für das Window-Feld.
|
||||
chunks = await asyncio.to_thread(strategy_sliding_window, blocks, config, note_id, context_prefix=h1_prefix)
|
||||
|
||||
if not chunks:
|
||||
return []
|
||||
|
||||
# 3. Physikalische Kontext-Anreicherung (Der Qualitäts-Fix)
|
||||
# WP-24c v4.2.6: Arbeite auf Original-Text inkl. Callouts (für korrekte Chunk-Attribution)
|
||||
# Schreibt Kanten aus Callouts/Inlines hart in den Text für Qdrant.
|
||||
chunks = propagate_section_edges(chunks)
|
||||
|
||||
# 5. WP-15b: Candidate Pool Aufbau (Metadaten für IngestionService)
|
||||
# WP-24c v4.2.7: Markiere Callout-Kanten explizit für Chunk-Attribution
|
||||
# 4. WP-15b: Candidate Pool Aufbau (Metadaten für IngestionService)
|
||||
# Zuerst die explizit im Text vorhandenen Kanten sammeln.
|
||||
# WP-24c v4.4.0-DEBUG: Schnittstelle 1 - Extraktion
|
||||
for idx, ch in enumerate(chunks):
|
||||
for ch in chunks:
|
||||
# Wir extrahieren aus dem bereits (durch Propagation) angereicherten Text.
|
||||
# ch.candidate_pool wird im Modell-Konstruktor als leere Liste initialisiert.
|
||||
for edge_info in parse_edges_robust(ch.text):
|
||||
edge_str = edge_info["edge"]
|
||||
is_callout = edge_info.get("is_callout", False)
|
||||
parts = edge_str.split(':', 1)
|
||||
for e_str in parse_edges_robust(ch.text):
|
||||
parts = e_str.split(':', 1)
|
||||
if len(parts) == 2:
|
||||
k, t = parts
|
||||
# WP-24c v4.2.7: Callout-Kanten erhalten explicit:callout Provenance
|
||||
# WP-24c v4.4.1: Harmonisierung - Provenance muss exakt "explicit:callout" sein
|
||||
provenance = "explicit:callout" if is_callout else "explicit"
|
||||
# WP-24c v4.4.1: Verwende "to" für Kompatibilität (wird auch in graph_derive_edges.py erwartet)
|
||||
# Zusätzlich "target_id" für maximale Kompatibilität mit ingestion_processor Validierung
|
||||
pool_entry = {"kind": k, "to": t, "provenance": provenance}
|
||||
if is_callout:
|
||||
# WP-24c v4.4.1: Für Callouts auch "target_id" hinzufügen für Validierung
|
||||
pool_entry["target_id"] = t
|
||||
ch.candidate_pool.append(pool_entry)
|
||||
ch.candidate_pool.append({"kind": k, "to": t, "provenance": "explicit"})
|
||||
|
||||
# WP-24c v4.4.0-DEBUG: Schnittstelle 1 - Logging
|
||||
if is_callout:
|
||||
logger.debug(f"DEBUG-TRACER [Extraction]: Chunk Index: {idx}, Chunk ID: {ch.id}, Kind: {k}, Target: {t}, Provenance: {provenance}, Is_Callout: {is_callout}, Raw_Edge_Str: {edge_str}")
|
||||
|
||||
# 6. Global Pool (Unzugeordnete Kanten - kann mitten im Dokument oder am Ende stehen)
|
||||
# WP-24c v4.2.0: Konfigurierbare Header-Namen und -Ebene via .env
|
||||
# Sucht nach ALLEN Edge-Pool Blöcken im Original-Markdown (nicht nur am Ende).
|
||||
llm_validation_headers = os.getenv(
|
||||
"MINDNET_LLM_VALIDATION_HEADERS",
|
||||
"Unzugeordnete Kanten,Edge Pool,Candidates"
|
||||
# 5. Global Pool (Unzugeordnete Kanten aus dem Dokument-Ende)
|
||||
# Sucht nach dem Edge-Pool Block im Original-Markdown.
|
||||
pool_match = re.search(
|
||||
r'###?\s*(?:Unzugeordnete Kanten|Edge Pool|Candidates)\s*\n(.*?)(?:\n#|$)',
|
||||
body_text,
|
||||
re.DOTALL | re.IGNORECASE
|
||||
)
|
||||
header_list = [h.strip() for h in llm_validation_headers.split(",") if h.strip()]
|
||||
# Fallback auf Defaults, falls leer
|
||||
if not header_list:
|
||||
header_list = ["Unzugeordnete Kanten", "Edge Pool", "Candidates"]
|
||||
|
||||
# Header-Ebene konfigurierbar (Default: 3 für ###)
|
||||
llm_validation_level = int(os.getenv("MINDNET_LLM_VALIDATION_HEADER_LEVEL", "3"))
|
||||
header_level_pattern = "#" * llm_validation_level
|
||||
|
||||
# Regex-Pattern mit konfigurierbaren Headern und Ebene
|
||||
# WP-24c v4.2.0: finditer statt search, um ALLE Zonen zu finden (auch mitten im Dokument)
|
||||
# Zone endet bei einem neuen Header (jeder Ebene) oder am Dokument-Ende
|
||||
header_pattern = "|".join(re.escape(h) for h in header_list)
|
||||
zone_pattern = rf'^{re.escape(header_level_pattern)}\s*(?:{header_pattern})\s*\n(.*?)(?=\n#|$)'
|
||||
|
||||
for pool_match in re.finditer(zone_pattern, body_text, re.DOTALL | re.IGNORECASE | re.MULTILINE):
|
||||
if pool_match:
|
||||
global_edges = parse_edges_robust(pool_match.group(1))
|
||||
for edge_info in global_edges:
|
||||
edge_str = edge_info["edge"]
|
||||
parts = edge_str.split(':', 1)
|
||||
for e_str in global_edges:
|
||||
parts = e_str.split(':', 1)
|
||||
if len(parts) == 2:
|
||||
k, t = parts
|
||||
# Diese Kanten werden als "global_pool" markiert für die spätere KI-Prüfung.
|
||||
# Diese Kanten werden als "Global Pool" markiert für die spätere KI-Prüfung.
|
||||
for ch in chunks:
|
||||
ch.candidate_pool.append({"kind": k, "to": t, "provenance": "global_pool"})
|
||||
|
||||
# 7. De-Duplikation des Pools & Linking
|
||||
# 6. De-Duplikation des Pools & Linking
|
||||
for ch in chunks:
|
||||
seen = set()
|
||||
unique = []
|
||||
for c in ch.candidate_pool:
|
||||
# Eindeutigkeit über Typ, Ziel und Herkunft (Provenance)
|
||||
key = (c["kind"], c["to"], c["provenance"])
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
unique.append(c)
|
||||
ch.candidate_pool = unique
|
||||
|
||||
# 8. WP-24c v4.2.6: Clean-Context - Entferne Callout-Syntax aus Chunk-Text
|
||||
# WICHTIG: Dies geschieht NACH propagate_section_edges und Candidate Pool Aufbau,
|
||||
# damit Chunk-Attribution erhalten bleibt und Kanten korrekt extrahiert werden.
|
||||
# Hinweis: Callouts können mehrzeilig sein (auch verschachtelt: >>)
|
||||
def remove_callouts_from_text(text: str) -> str:
|
||||
"""Entfernt alle Callout-Zeilen (> [!edge] oder > [!abstract]) aus dem Text."""
|
||||
if not text:
|
||||
return text
|
||||
|
||||
lines = text.split('\n')
|
||||
cleaned_lines = []
|
||||
i = 0
|
||||
|
||||
# NEU (v4.2.8):
|
||||
# WP-24c v4.2.8: Callout-Pattern für Edge und Abstract
|
||||
callout_start_pattern = re.compile(r'^>\s*\[!(edge|abstract)[^\]]*\]', re.IGNORECASE)
|
||||
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
callout_match = callout_start_pattern.match(line)
|
||||
|
||||
if callout_match:
|
||||
# Callout gefunden: Überspringe alle Zeilen des Callout-Blocks
|
||||
leading_gt_count = len(line) - len(line.lstrip('>'))
|
||||
i += 1
|
||||
|
||||
# Überspringe alle Zeilen, die zum Callout gehören
|
||||
while i < len(lines):
|
||||
next_line = lines[i]
|
||||
if not next_line.strip().startswith('>'):
|
||||
break
|
||||
next_leading_gt = len(next_line) - len(next_line.lstrip('>'))
|
||||
if next_leading_gt < leading_gt_count:
|
||||
break
|
||||
i += 1
|
||||
else:
|
||||
# Normale Zeile: Behalte
|
||||
cleaned_lines.append(line)
|
||||
i += 1
|
||||
|
||||
# Normalisiere Leerzeilen (max. 2 aufeinanderfolgende)
|
||||
result = '\n'.join(cleaned_lines)
|
||||
result = re.sub(r'\n\s*\n\s*\n+', '\n\n', result)
|
||||
return result
|
||||
|
||||
for ch in chunks:
|
||||
ch.text = remove_callouts_from_text(ch.text)
|
||||
if ch.window:
|
||||
ch.window = remove_callouts_from_text(ch.window)
|
||||
|
||||
# Verknüpfung der Nachbarschaften für Graph-Traversierung
|
||||
for i, ch in enumerate(chunks):
|
||||
ch.neighbors_prev = chunks[i-1].id if i > 0 else None
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
"""
|
||||
FILE: app/core/chunking/chunking_propagation.py
|
||||
DESCRIPTION: Injiziert Sektions-Kanten physisch in den Text (Embedding-Enrichment).
|
||||
Fix v3.3.6: Nutzt robustes Parsing zur Erkennung vorhandener Kanten,
|
||||
um Dopplungen direkt hinter [!edge] Callouts format-agnostisch zu verhindern.
|
||||
Stellt die "Gold-Standard"-Qualität von v3.1.0 wieder her.
|
||||
VERSION: 3.3.1
|
||||
STATUS: Active
|
||||
"""
|
||||
from typing import List, Dict, Set
|
||||
from .chunking_models import Chunk
|
||||
|
|
@ -11,7 +12,7 @@ from .chunking_parser import parse_edges_robust
|
|||
def propagate_section_edges(chunks: List[Chunk]) -> List[Chunk]:
|
||||
"""
|
||||
Sammelt Kanten pro Sektion und schreibt sie hart in den Text und das Window.
|
||||
Verhindert Dopplungen, wenn Kanten bereits via [!edge] Callout vorhanden sind.
|
||||
Dies ist essenziell für die Vektorisierung der Beziehungen.
|
||||
"""
|
||||
# 1. Sammeln: Alle expliziten Kanten pro Sektions-Pfad aggregieren
|
||||
section_map: Dict[str, Set[str]] = {} # path -> set(kind:target)
|
||||
|
|
@ -22,13 +23,11 @@ def propagate_section_edges(chunks: List[Chunk]) -> List[Chunk]:
|
|||
continue
|
||||
|
||||
# Nutzt den robusten Parser aus dem Package
|
||||
# WP-24c v4.2.7: parse_edges_robust gibt jetzt Liste von Dicts zurück
|
||||
edge_infos = parse_edges_robust(ch.text)
|
||||
if edge_infos:
|
||||
edges = parse_edges_robust(ch.text)
|
||||
if edges:
|
||||
if ch.section_path not in section_map:
|
||||
section_map[ch.section_path] = set()
|
||||
for edge_info in edge_infos:
|
||||
section_map[ch.section_path].add(edge_info["edge"])
|
||||
section_map[ch.section_path].update(edges)
|
||||
|
||||
# 2. Injizieren: Kanten in jeden Chunk der Sektion zurückschreiben (Broadcasting)
|
||||
for ch in chunks:
|
||||
|
|
@ -37,30 +36,21 @@ def propagate_section_edges(chunks: List[Chunk]) -> List[Chunk]:
|
|||
if not edges_to_add:
|
||||
continue
|
||||
|
||||
# Vorhandene Kanten (Typ:Ziel) in DIESEM Chunk ermitteln,
|
||||
# um Dopplungen (z.B. durch Callouts) zu vermeiden.
|
||||
# WP-24c v4.2.7: parse_edges_robust gibt jetzt Liste von Dicts zurück
|
||||
existing_edge_infos = parse_edges_robust(ch.text)
|
||||
existing_edges = {ei["edge"] for ei in existing_edge_infos}
|
||||
|
||||
injections = []
|
||||
# Sortierung für deterministische Ergebnisse
|
||||
for e_str in sorted(list(edges_to_add)):
|
||||
# Wenn die Kante (Typ + Ziel) bereits vorhanden ist (egal welches Format),
|
||||
# überspringen wir die Injektion für diesen Chunk.
|
||||
if e_str in existing_edges:
|
||||
continue
|
||||
|
||||
for e_str in edges_to_add:
|
||||
kind, target = e_str.split(':', 1)
|
||||
injections.append(f"[[rel:{kind}|{target}]]")
|
||||
# Nur injizieren, wenn die Kante nicht bereits im Text steht
|
||||
token = f"[[rel:{kind}|{target}]]"
|
||||
if token not in ch.text:
|
||||
injections.append(token)
|
||||
|
||||
if injections:
|
||||
# Physische Anreicherung
|
||||
# Physische Anreicherung (Der v3.1.0 Qualitäts-Fix)
|
||||
# Triple-Newline für saubere Trennung im Embedding-Fenster
|
||||
block = "\n\n\n" + " ".join(injections)
|
||||
ch.text += block
|
||||
|
||||
# Auch ins Window schreiben, da Qdrant hier sucht!
|
||||
# ENTSCHEIDEND: Auch ins Window schreiben, da Qdrant hier sucht!
|
||||
if ch.window:
|
||||
ch.window += block
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,190 +1,142 @@
|
|||
"""
|
||||
FILE: app/core/chunking/chunking_strategies.py
|
||||
DESCRIPTION: Strategien für atomares Sektions-Chunking v3.9.9.
|
||||
Implementiert das 'Pack-and-Carry-Over' Verfahren nach Regel 1-3.
|
||||
- Keine redundante Kanten-Injektion.
|
||||
- Strikte Einhaltung von Sektionsgrenzen via Look-Ahead.
|
||||
- Fix: Synchronisierung der Parameter mit dem Orchestrator (context_prefix).
|
||||
WP-24c v4.2.5: Strict-Mode ohne Carry-Over - Bei strict_heading_split wird nach jeder Sektion geflasht.
|
||||
DESCRIPTION: Mathematische Splitting-Strategien.
|
||||
AUDIT v3.3.2: 100% Konformität zur 'by_heading' Spezifikation.
|
||||
- Implementiert Hybrid-Safety-Net (Sliding Window für Übergrößen).
|
||||
- Breadcrumb-Kontext im Window (H1 > H2).
|
||||
- Sliding Window mit H1-Kontext (Gold-Standard v3.1.0).
|
||||
"""
|
||||
from typing import List, Dict, Any, Optional
|
||||
from .chunking_models import RawBlock, Chunk
|
||||
from .chunking_utils import estimate_tokens
|
||||
from .chunking_parser import split_sentences
|
||||
|
||||
def _create_win(context_prefix: str, sec_title: Optional[str], text: str) -> str:
|
||||
def _create_context_win(doc_title: str, sec_title: Optional[str], text: str) -> str:
|
||||
"""Baut den Breadcrumb-Kontext für das Embedding-Fenster."""
|
||||
parts = [context_prefix] if context_prefix else []
|
||||
# Verhindert Dopplung, falls der Context-Prefix (H1) bereits den Sektionsnamen enthält
|
||||
if sec_title and f"# {sec_title}" != context_prefix and sec_title not in (context_prefix or ""):
|
||||
parts.append(sec_title)
|
||||
parts = []
|
||||
if doc_title: parts.append(doc_title)
|
||||
if sec_title and sec_title != doc_title: parts.append(sec_title)
|
||||
prefix = " > ".join(parts)
|
||||
return f"{prefix}\n{text}".strip() if prefix else text
|
||||
|
||||
def strategy_by_heading(blocks: List[RawBlock], config: Dict[str, Any], note_id: str, context_prefix: str = "") -> List[Chunk]:
|
||||
def strategy_sliding_window(blocks: List[RawBlock],
|
||||
config: Dict[str, Any],
|
||||
note_id: str,
|
||||
context_prefix: str = "") -> List[Chunk]:
|
||||
"""
|
||||
Universelle Heading-Strategie mit Carry-Over Logik.
|
||||
Synchronisiert auf context_prefix für Kompatibilität mit dem Orchestrator.
|
||||
Fasst Blöcke zusammen und schneidet bei 'target' Tokens.
|
||||
Ignoriert H2-Überschriften beim Splitting, um Kontext zu wahren.
|
||||
"""
|
||||
target = config.get("target", 400)
|
||||
max_tokens = config.get("max", 600)
|
||||
overlap_val = config.get("overlap", (50, 80))
|
||||
overlap = sum(overlap_val) // 2 if isinstance(overlap_val, tuple) else overlap_val
|
||||
|
||||
chunks: List[Chunk] = []
|
||||
buf: List[RawBlock] = []
|
||||
|
||||
def _add(txt, sec, path):
|
||||
idx = len(chunks)
|
||||
# H1-Kontext Präfix für das Window-Feld
|
||||
win = f"{context_prefix}\n{txt}".strip() if context_prefix else txt
|
||||
chunks.append(Chunk(
|
||||
id=f"{note_id}#c{idx:02d}", note_id=note_id, index=idx,
|
||||
text=txt, window=win, token_count=estimate_tokens(txt),
|
||||
section_title=sec, section_path=path,
|
||||
neighbors_prev=None, neighbors_next=None
|
||||
))
|
||||
|
||||
def flush():
|
||||
nonlocal buf
|
||||
if not buf: return
|
||||
text_body = "\n\n".join([b.text for b in buf])
|
||||
sec_title = buf[-1].section_title; sec_path = buf[-1].section_path
|
||||
|
||||
if estimate_tokens(text_body) <= max_tokens:
|
||||
_add(text_body, sec_title, sec_path)
|
||||
else:
|
||||
sents = split_sentences(text_body); cur_sents = []; cur_len = 0
|
||||
for s in sents:
|
||||
slen = estimate_tokens(s)
|
||||
if cur_len + slen > target and cur_sents:
|
||||
_add(" ".join(cur_sents), sec_title, sec_path)
|
||||
ov_s = []; ov_l = 0
|
||||
for os in reversed(cur_sents):
|
||||
if ov_l + estimate_tokens(os) < overlap:
|
||||
ov_s.insert(0, os); ov_l += estimate_tokens(os)
|
||||
else: break
|
||||
cur_sents = list(ov_s); cur_sents.append(s); cur_len = ov_l + slen
|
||||
else:
|
||||
cur_sents.append(s); cur_len += slen
|
||||
if cur_sents:
|
||||
_add(" ".join(cur_sents), sec_title, sec_path)
|
||||
buf = []
|
||||
|
||||
for b in blocks:
|
||||
# H2-Überschriften werden ignoriert, um den Zusammenhang zu wahren
|
||||
if b.kind == "heading": continue
|
||||
if estimate_tokens("\n\n".join([x.text for x in buf])) + estimate_tokens(b.text) >= target:
|
||||
flush()
|
||||
buf.append(b)
|
||||
flush()
|
||||
return chunks
|
||||
|
||||
def strategy_by_heading(blocks: List[RawBlock], config: Dict[str, Any], note_id: str, doc_title: str = "") -> List[Chunk]:
|
||||
"""
|
||||
Splittet Text basierend auf Markdown-Überschriften mit Hybrid-Safety-Net.
|
||||
"""
|
||||
smart_edge = config.get("enable_smart_edge_allocation", True)
|
||||
strict = config.get("strict_heading_split", False)
|
||||
target = config.get("target", 400)
|
||||
max_tokens = config.get("max", 600)
|
||||
split_level = config.get("split_level", 2)
|
||||
overlap_cfg = config.get("overlap", (50, 80))
|
||||
overlap = sum(overlap_cfg) // 2 if isinstance(overlap_cfg, (list, tuple)) else overlap_cfg
|
||||
overlap = sum(config.get("overlap", (50, 80))) // 2
|
||||
|
||||
chunks: List[Chunk] = []
|
||||
buf: List[str] = []
|
||||
cur_tokens = 0
|
||||
|
||||
def _emit(txt, title, path):
|
||||
"""Schreibt den finalen Chunk ohne Text-Modifikationen."""
|
||||
def _add_to_chunks(txt, title, path):
|
||||
idx = len(chunks)
|
||||
win = _create_win(context_prefix, title, txt)
|
||||
win = _create_context_win(doc_title, title, txt)
|
||||
chunks.append(Chunk(
|
||||
id=f"{note_id}#c{idx:02d}", note_id=note_id, index=idx,
|
||||
text=txt, window=win, token_count=estimate_tokens(txt),
|
||||
section_title=title, section_path=path, neighbors_prev=None, neighbors_next=None
|
||||
section_title=title, section_path=path,
|
||||
neighbors_prev=None, neighbors_next=None
|
||||
))
|
||||
|
||||
# --- SCHRITT 1: Gruppierung in atomare Sektions-Einheiten ---
|
||||
sections: List[Dict[str, Any]] = []
|
||||
curr_blocks = []
|
||||
def _flush(title, path):
|
||||
nonlocal buf, cur_tokens
|
||||
if not buf: return
|
||||
full_text = "\n\n".join(buf)
|
||||
if estimate_tokens(full_text) <= max_tokens:
|
||||
_add_to_chunks(full_text, title, path)
|
||||
else:
|
||||
sents = split_sentences(full_text); cur_sents = []; sub_len = 0
|
||||
for s in sents:
|
||||
slen = estimate_tokens(s)
|
||||
if sub_len + slen > target and cur_sents:
|
||||
_add_to_chunks(" ".join(cur_sents), title, path)
|
||||
ov_s = []; ov_l = 0
|
||||
for os in reversed(cur_sents):
|
||||
if ov_l + estimate_tokens(os) < overlap:
|
||||
ov_s.insert(0, os); ov_l += estimate_tokens(os)
|
||||
else: break
|
||||
cur_sents = list(ov_s); cur_sents.append(s); sub_len = ov_l + slen
|
||||
else: cur_sents.append(s); sub_len += slen
|
||||
if cur_sents: _add_to_chunks(" ".join(cur_sents), title, path)
|
||||
buf = []; cur_tokens = 0
|
||||
|
||||
for b in blocks:
|
||||
if b.kind == "heading" and b.level <= split_level:
|
||||
if curr_blocks:
|
||||
sections.append({
|
||||
"text": "\n\n".join([x.text for x in curr_blocks]),
|
||||
"meta": curr_blocks[0],
|
||||
"is_empty": len(curr_blocks) == 1 and curr_blocks[0].kind == "heading"
|
||||
})
|
||||
curr_blocks = [b]
|
||||
else:
|
||||
curr_blocks.append(b)
|
||||
if curr_blocks:
|
||||
sections.append({
|
||||
"text": "\n\n".join([x.text for x in curr_blocks]),
|
||||
"meta": curr_blocks[0],
|
||||
"is_empty": len(curr_blocks) == 1 and curr_blocks[0].kind == "heading"
|
||||
})
|
||||
|
||||
# --- SCHRITT 2: Verarbeitung der Queue ---
|
||||
queue = list(sections)
|
||||
current_chunk_text = ""
|
||||
current_meta = {"title": None, "path": "/"}
|
||||
|
||||
# Bestimmung des Modus: Hard-Split wenn smart_edge=False ODER strict=True
|
||||
is_hard_split_mode = (not smart_edge) or (strict)
|
||||
|
||||
while queue:
|
||||
item = queue.pop(0)
|
||||
item_text = item["text"]
|
||||
|
||||
# Initialisierung für neuen Chunk
|
||||
if not current_chunk_text:
|
||||
current_meta["title"] = item["meta"].section_title
|
||||
current_meta["path"] = item["meta"].section_path
|
||||
|
||||
# FALL A: HARD SPLIT MODUS (WP-24c v4.2.5: Strict-Mode ohne Carry-Over)
|
||||
if is_hard_split_mode:
|
||||
# WP-24c v4.2.5: Bei strict_heading_split: true wird nach JEDER Sektion geflasht
|
||||
# Kein Carry-Over erlaubt, auch nicht für leere Überschriften
|
||||
if current_chunk_text:
|
||||
# Flashe vorherigen Chunk
|
||||
_emit(current_chunk_text, current_meta["title"], current_meta["path"])
|
||||
current_chunk_text = ""
|
||||
|
||||
# Neue Sektion: Initialisiere Meta
|
||||
current_meta["title"] = item["meta"].section_title
|
||||
current_meta["path"] = item["meta"].section_path
|
||||
|
||||
# WP-24c v4.2.5: Auch leere Sektionen werden als separater Chunk erstellt
|
||||
# (nur Überschrift, kein Inhalt)
|
||||
if item.get("is_empty", False):
|
||||
# Leere Sektion: Nur Überschrift als Chunk
|
||||
_emit(item_text, current_meta["title"], current_meta["path"])
|
||||
else:
|
||||
# Normale Sektion: Prüfe auf Token-Limit
|
||||
if estimate_tokens(item_text) > max_tokens:
|
||||
# Sektion zu groß: Smart Zerlegung (aber trotzdem in separaten Chunks)
|
||||
sents = split_sentences(item_text)
|
||||
header_prefix = item["meta"].text if item["meta"].kind == "heading" else ""
|
||||
|
||||
take_sents = []; take_len = 0
|
||||
while sents:
|
||||
s = sents.pop(0); slen = estimate_tokens(s)
|
||||
if take_len + slen > target and take_sents:
|
||||
_emit(" ".join(take_sents), current_meta["title"], current_meta["path"])
|
||||
take_sents = [s]; take_len = slen
|
||||
else:
|
||||
take_sents.append(s); take_len += slen
|
||||
|
||||
if take_sents:
|
||||
_emit(" ".join(take_sents), current_meta["title"], current_meta["path"])
|
||||
else:
|
||||
# Sektion passt: Direkt als Chunk
|
||||
_emit(item_text, current_meta["title"], current_meta["path"])
|
||||
|
||||
current_chunk_text = ""
|
||||
if b.kind == "heading":
|
||||
if b.level < split_level: _flush(b.section_title, b.section_path)
|
||||
elif b.level == split_level:
|
||||
if strict or cur_tokens >= target: _flush(b.section_title, b.section_path)
|
||||
continue
|
||||
|
||||
# FALL B: SMART MODE (Regel 1-3)
|
||||
combined_text = (current_chunk_text + "\n\n" + item_text).strip() if current_chunk_text else item_text
|
||||
combined_est = estimate_tokens(combined_text)
|
||||
|
||||
if combined_est <= max_tokens:
|
||||
# Regel 1 & 2: Passt rein laut Schätzung -> Aufnehmen
|
||||
current_chunk_text = combined_text
|
||||
else:
|
||||
if current_chunk_text:
|
||||
# Regel 2: Flashen an Sektionsgrenze, Item zurücklegen
|
||||
_emit(current_chunk_text, current_meta["title"], current_meta["path"])
|
||||
current_chunk_text = ""
|
||||
queue.insert(0, item)
|
||||
else:
|
||||
# Regel 3: Einzelne Sektion zu groß -> Smart Zerlegung
|
||||
sents = split_sentences(item_text)
|
||||
header_prefix = item["meta"].text if item["meta"].kind == "heading" else ""
|
||||
|
||||
take_sents = []; take_len = 0
|
||||
while sents:
|
||||
s = sents.pop(0); slen = estimate_tokens(s)
|
||||
if take_len + slen > target and take_sents:
|
||||
sents.insert(0, s); break
|
||||
take_sents.append(s); take_len += slen
|
||||
|
||||
_emit(" ".join(take_sents), current_meta["title"], current_meta["path"])
|
||||
|
||||
if sents:
|
||||
remainder = " ".join(sents)
|
||||
# Kontext-Erhalt: Überschrift für den Rest wiederholen
|
||||
if header_prefix and not remainder.startswith(header_prefix):
|
||||
remainder = header_prefix + "\n\n" + remainder
|
||||
# Carry-Over: Rest wird vorne in die Queue geschoben
|
||||
queue.insert(0, {"text": remainder, "meta": item["meta"], "is_split": True})
|
||||
|
||||
if current_chunk_text:
|
||||
_emit(current_chunk_text, current_meta["title"], current_meta["path"])
|
||||
|
||||
return chunks
|
||||
|
||||
def strategy_sliding_window(blocks: List[RawBlock], config: Dict[str, Any], note_id: str, context_prefix: str = "") -> List[Chunk]:
|
||||
"""Standard-Sliding-Window für flache Texte ohne Sektionsfokus."""
|
||||
target = config.get("target", 400); max_tokens = config.get("max", 600)
|
||||
chunks: List[Chunk] = []; buf: List[RawBlock] = []
|
||||
|
||||
for b in blocks:
|
||||
b_tokens = estimate_tokens(b.text)
|
||||
curr_tokens = sum(estimate_tokens(x.text) for x in buf) if buf else 0
|
||||
if curr_tokens + b_tokens > max_tokens and buf:
|
||||
txt = "\n\n".join([x.text for x in buf]); idx = len(chunks)
|
||||
win = _create_win(context_prefix, buf[0].section_title, txt)
|
||||
chunks.append(Chunk(id=f"{note_id}#c{idx:02d}", note_id=note_id, index=idx, text=txt, window=win, token_count=curr_tokens, section_title=buf[0].section_title, section_path=buf[0].section_path, neighbors_prev=None, neighbors_next=None))
|
||||
buf = []
|
||||
buf.append(b)
|
||||
|
||||
bt = estimate_tokens(b.text)
|
||||
if cur_tokens + bt > max_tokens and buf: _flush(b.section_title, b.section_path)
|
||||
buf.append(b.text); cur_tokens += bt
|
||||
if buf:
|
||||
txt = "\n\n".join([x.text for x in buf]); idx = len(chunks)
|
||||
win = _create_win(context_prefix, buf[0].section_title, txt)
|
||||
chunks.append(Chunk(id=f"{note_id}#c{idx:02d}", note_id=note_id, index=idx, text=txt, window=win, token_count=estimate_tokens(txt), section_title=buf[0].section_title, section_path=buf[0].section_path, neighbors_prev=None, neighbors_next=None))
|
||||
|
||||
last_b = blocks[-1] if blocks else None
|
||||
_flush(last_b.section_title if last_b else None, last_b.section_path if last_b else "/")
|
||||
return chunks
|
||||
|
|
@ -6,7 +6,7 @@ import math
|
|||
import yaml
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Tuple, Optional
|
||||
from typing import Dict, Any, Tuple
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -27,31 +27,12 @@ def load_yaml_config() -> Dict[str, Any]:
|
|||
return data
|
||||
except Exception: return {}
|
||||
|
||||
def get_chunk_config(note_type: str, frontmatter: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Lädt die Chunking-Strategie basierend auf dem Note-Type.
|
||||
WP-24c v4.2.5: Frontmatter-Override für chunking_profile hat höchste Priorität.
|
||||
|
||||
Args:
|
||||
note_type: Der Typ der Note (z.B. "decision", "experience")
|
||||
frontmatter: Optionales Frontmatter-Dict mit chunking_profile Override
|
||||
|
||||
Returns:
|
||||
Dict mit Chunking-Konfiguration
|
||||
"""
|
||||
def get_chunk_config(note_type: str) -> Dict[str, Any]:
|
||||
"""Lädt die Chunking-Strategie basierend auf dem Note-Type."""
|
||||
full_config = load_yaml_config()
|
||||
profiles = full_config.get("chunking_profiles", {})
|
||||
type_def = full_config.get("types", {}).get(note_type.lower(), {})
|
||||
|
||||
# WP-24c v4.2.5: Priorität: Frontmatter > Type-Def > Defaults
|
||||
profile_name = None
|
||||
if frontmatter and "chunking_profile" in frontmatter:
|
||||
profile_name = frontmatter.get("chunking_profile") or frontmatter.get("chunk_profile")
|
||||
if not profile_name:
|
||||
profile_name = type_def.get("chunking_profile")
|
||||
if not profile_name:
|
||||
profile_name = full_config.get("defaults", {}).get("chunking_profile", "sliding_standard")
|
||||
|
||||
profile_name = type_def.get("chunking_profile") or full_config.get("defaults", {}).get("chunking_profile", "sliding_standard")
|
||||
config = profiles.get(profile_name, DEFAULT_PROFILE).copy()
|
||||
if "overlap" in config and isinstance(config["overlap"], list):
|
||||
config["overlap"] = tuple(config["overlap"])
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ FILE: app/core/database/qdrant.py
|
|||
DESCRIPTION: Qdrant-Client Factory und Schema-Management.
|
||||
Erstellt Collections und Payload-Indizes.
|
||||
MODULARISIERUNG: Verschoben in das database-Paket für WP-14.
|
||||
VERSION: 2.2.2 (WP-Fix: Index für target_section)
|
||||
VERSION: 2.2.1
|
||||
STATUS: Active
|
||||
DEPENDENCIES: qdrant_client, dataclasses, os
|
||||
"""
|
||||
|
|
@ -44,9 +44,7 @@ class QdrantConfig:
|
|||
port = os.getenv("QDRANT_PORT")
|
||||
port = int(port) if port else None
|
||||
api_key = os.getenv("QDRANT_API_KEY") or None
|
||||
# WP-24c v4.5.10: Harmonisierung - Unterstützt beide Umgebungsvariablen für Abwärtskompatibilität
|
||||
# COLLECTION_PREFIX hat Priorität, MINDNET_PREFIX als Fallback
|
||||
prefix = os.getenv("COLLECTION_PREFIX") or os.getenv("MINDNET_PREFIX") or "mindnet"
|
||||
prefix = os.getenv("COLLECTION_PREFIX") or "mindnet"
|
||||
dim = int(os.getenv("VECTOR_DIM") or 384)
|
||||
distance = os.getenv("DISTANCE", "Cosine")
|
||||
on_disk_payload = (os.getenv("ON_DISK_PAYLOAD", "true").lower() == "true")
|
||||
|
|
@ -126,7 +124,7 @@ def ensure_payload_indexes(client: QdrantClient, prefix: str) -> None:
|
|||
Stellt sicher, dass alle benötigten Payload-Indizes für die Suche existieren.
|
||||
- notes: note_id, type, title, updated, tags
|
||||
- chunks: note_id, chunk_id, index, type, tags
|
||||
- edges: note_id, kind, scope, source_id, target_id, chunk_id, target_section
|
||||
- edges: note_id, kind, scope, source_id, target_id, chunk_id
|
||||
"""
|
||||
notes, chunks, edges = collection_names(prefix)
|
||||
|
||||
|
|
@ -158,8 +156,6 @@ def ensure_payload_indexes(client: QdrantClient, prefix: str) -> None:
|
|||
("source_id", rest.PayloadSchemaType.KEYWORD),
|
||||
("target_id", rest.PayloadSchemaType.KEYWORD),
|
||||
("chunk_id", rest.PayloadSchemaType.KEYWORD),
|
||||
# NEU: Index für Section-Links (WP-15b)
|
||||
("target_section", rest.PayloadSchemaType.KEYWORD),
|
||||
]:
|
||||
_ensure_index(client, edges, field, schema)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
"""
|
||||
FILE: app/core/database/qdrant_points.py
|
||||
DESCRIPTION: Object-Mapper für Qdrant. Konvertiert JSON-Payloads (Notes, Chunks, Edges)
|
||||
in PointStructs und generiert deterministische UUIDs.
|
||||
VERSION: 4.1.0 (WP-24c: Gold-Standard Identity v4.1.0 - target_section Support)
|
||||
DESCRIPTION: Object-Mapper für Qdrant. Konvertiert JSON-Payloads (Notes, Chunks, Edges) in PointStructs und generiert deterministische UUIDs.
|
||||
VERSION: 1.5.0
|
||||
STATUS: Active
|
||||
DEPENDENCIES: qdrant_client, uuid, os, app.core.graph.graph_utils
|
||||
LAST_ANALYSIS: 2026-01-10
|
||||
DEPENDENCIES: qdrant_client, uuid, os
|
||||
LAST_ANALYSIS: 2025-12-15
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import os
|
||||
|
|
@ -15,44 +14,25 @@ from typing import List, Tuple, Iterable, Optional, Dict, Any
|
|||
from qdrant_client.http import models as rest
|
||||
from qdrant_client import QdrantClient
|
||||
|
||||
# WP-24c: Import der zentralen Identitäts-Logik zur Vermeidung von ID-Drift
|
||||
from app.core.graph.graph_utils import _mk_edge_id
|
||||
|
||||
# --------------------- ID helpers ---------------------
|
||||
|
||||
def _to_uuid(stable_key: str) -> str:
|
||||
"""
|
||||
Erzeugt eine deterministische UUIDv5 basierend auf einem stabilen Schlüssel.
|
||||
Härtung v1.5.2: Guard gegen leere Schlüssel zur Vermeidung von Pydantic-Fehlern.
|
||||
"""
|
||||
if not stable_key:
|
||||
raise ValueError("UUID generation failed: stable_key is empty or None")
|
||||
return str(uuid.uuid5(uuid.NAMESPACE_URL, str(stable_key)))
|
||||
return str(uuid.uuid5(uuid.NAMESPACE_URL, stable_key))
|
||||
|
||||
def _names(prefix: str) -> Tuple[str, str, str]:
|
||||
"""Interne Auflösung der Collection-Namen basierend auf dem Präfix."""
|
||||
return f"{prefix}_notes", f"{prefix}_chunks", f"{prefix}_edges"
|
||||
|
||||
# --------------------- Points builders ---------------------
|
||||
|
||||
def points_for_note(prefix: str, note_payload: dict, note_vec: List[float] | None, dim: int) -> Tuple[str, List[rest.PointStruct]]:
|
||||
"""Konvertiert Note-Metadaten in Qdrant Points."""
|
||||
notes_col, _, _ = _names(prefix)
|
||||
# Nutzt Null-Vektor als Fallback, falls kein Embedding vorhanden ist
|
||||
vector = note_vec if note_vec is not None else [0.0] * int(dim)
|
||||
|
||||
raw_note_id = note_payload.get("note_id") or note_payload.get("id") or "missing-note-id"
|
||||
point_id = _to_uuid(raw_note_id)
|
||||
|
||||
pt = rest.PointStruct(
|
||||
id=point_id,
|
||||
vector=vector,
|
||||
payload=note_payload
|
||||
)
|
||||
pt = rest.PointStruct(id=point_id, vector=vector, payload=note_payload)
|
||||
return notes_col, [pt]
|
||||
|
||||
def points_for_chunks(prefix: str, chunk_payloads: List[dict], vectors: List[List[float]]) -> Tuple[str, List[rest.PointStruct]]:
|
||||
"""Konvertiert Chunks und deren Vektoren in Qdrant Points."""
|
||||
_, chunks_col, _ = _names(prefix)
|
||||
points: List[rest.PointStruct] = []
|
||||
for i, (pl, vec) in enumerate(zip(chunk_payloads, vectors), start=1):
|
||||
|
|
@ -61,93 +41,43 @@ def points_for_chunks(prefix: str, chunk_payloads: List[dict], vectors: List[Lis
|
|||
note_id = pl.get("note_id") or pl.get("parent_note_id") or "missing-note"
|
||||
chunk_id = f"{note_id}#{i}"
|
||||
pl["chunk_id"] = chunk_id
|
||||
|
||||
point_id = _to_uuid(chunk_id)
|
||||
points.append(rest.PointStruct(
|
||||
id=point_id,
|
||||
vector=vec,
|
||||
payload=pl
|
||||
))
|
||||
points.append(rest.PointStruct(id=point_id, vector=vec, payload=pl))
|
||||
return chunks_col, points
|
||||
|
||||
def _normalize_edge_payload(pl: dict) -> dict:
|
||||
"""Normalisiert Edge-Felder und sichert Schema-Konformität."""
|
||||
kind = pl.get("kind") or pl.get("edge_type") or "edge"
|
||||
source_id = pl.get("source_id") or pl.get("src_id") or "unknown-src"
|
||||
target_id = pl.get("target_id") or pl.get("dst_id") or "unknown-tgt"
|
||||
seq = pl.get("seq") or pl.get("order") or pl.get("index")
|
||||
|
||||
# WP-Fix: target_section explizit durchreichen
|
||||
target_section = pl.get("target_section")
|
||||
|
||||
pl.setdefault("kind", kind)
|
||||
pl.setdefault("source_id", source_id)
|
||||
pl.setdefault("target_id", target_id)
|
||||
|
||||
if seq is not None and "seq" not in pl:
|
||||
pl["seq"] = seq
|
||||
|
||||
if target_section is not None:
|
||||
pl["target_section"] = target_section
|
||||
|
||||
return pl
|
||||
|
||||
def points_for_edges(prefix: str, edge_payloads: List[dict]) -> Tuple[str, List[rest.PointStruct]]:
|
||||
"""
|
||||
Konvertiert Kanten-Payloads in PointStructs.
|
||||
WP-24c v4.1.0: Nutzt die zentrale _mk_edge_id Funktion aus graph_utils.
|
||||
Dies eliminiert den ID-Drift zwischen manuellen und virtuellen Kanten.
|
||||
|
||||
GOLD-STANDARD v4.1.0: Die ID-Generierung verwendet 4 Parameter + optional target_section
|
||||
(kind, source_id, target_id, scope, target_section).
|
||||
rule_id und variant werden ignoriert, target_section fließt ein (Multigraph-Support).
|
||||
"""
|
||||
_, _, edges_col = _names(prefix)
|
||||
points: List[rest.PointStruct] = []
|
||||
|
||||
for raw in edge_payloads:
|
||||
pl = _normalize_edge_payload(raw)
|
||||
|
||||
# Extraktion der Identitäts-Parameter (GOLD-STANDARD v4.1.0)
|
||||
edge_id = pl.get("edge_id")
|
||||
if not edge_id:
|
||||
kind = pl.get("kind", "edge")
|
||||
s = pl.get("source_id", "unknown-src")
|
||||
t = pl.get("target_id", "unknown-tgt")
|
||||
scope = pl.get("scope", "note")
|
||||
target_section = pl.get("target_section") # WP-24c v4.1.0: target_section für Section-Links
|
||||
|
||||
# Hinweis: rule_id und variant werden im Payload gespeichert,
|
||||
# fließen aber NICHT in die ID-Generierung ein (v4.0.0 Standard)
|
||||
# target_section fließt in die ID ein (v4.1.0: Multigraph-Support für Section-Links)
|
||||
|
||||
try:
|
||||
# Aufruf der Single-Source-of-Truth für IDs
|
||||
# GOLD-STANDARD v4.1.0: 4 Parameter + optional target_section
|
||||
point_id = _mk_edge_id(
|
||||
kind=kind,
|
||||
s=s,
|
||||
t=t,
|
||||
scope=scope,
|
||||
target_section=target_section
|
||||
)
|
||||
|
||||
# Synchronisierung des Payloads mit der berechneten ID
|
||||
pl["edge_id"] = point_id
|
||||
|
||||
points.append(rest.PointStruct(
|
||||
id=point_id,
|
||||
vector=[0.0],
|
||||
payload=pl
|
||||
))
|
||||
except ValueError as e:
|
||||
# Fehlerhaft definierte Kanten werden übersprungen, um Pydantic-Crashes zu vermeiden
|
||||
continue
|
||||
|
||||
seq = pl.get("seq") or ""
|
||||
edge_id = f"{kind}:{s}->{t}#{seq}"
|
||||
pl["edge_id"] = edge_id
|
||||
point_id = _to_uuid(edge_id)
|
||||
points.append(rest.PointStruct(id=point_id, vector=[0.0], payload=pl))
|
||||
return edges_col, points
|
||||
|
||||
# --------------------- Vector schema & overrides ---------------------
|
||||
|
||||
def _preferred_name(candidates: List[str]) -> str:
|
||||
"""Ermittelt den primären Vektor-Namen aus einer Liste von Kandidaten."""
|
||||
for k in ("text", "default", "embedding", "content"):
|
||||
if k in candidates:
|
||||
return k
|
||||
|
|
@ -155,11 +85,10 @@ def _preferred_name(candidates: List[str]) -> str:
|
|||
|
||||
def _env_override_for_collection(collection: str) -> Optional[str]:
|
||||
"""
|
||||
Prüft auf Umgebungsvariablen-Overrides für Vektor-Namen.
|
||||
Returns:
|
||||
- "__single__" für erzwungenen Single-Vector Modus
|
||||
- Name (str) für spezifischen Named-Vector
|
||||
- None für automatische Erkennung
|
||||
- "__single__" to force single-vector
|
||||
- concrete name (str) to force named-vector with that name
|
||||
- None to auto-detect
|
||||
"""
|
||||
base = os.getenv("MINDNET_VECTOR_NAME")
|
||||
if collection.endswith("_notes"):
|
||||
|
|
@ -174,17 +103,19 @@ def _env_override_for_collection(collection: str) -> Optional[str]:
|
|||
val = base.strip()
|
||||
if val.lower() in ("__single__", "single"):
|
||||
return "__single__"
|
||||
return val
|
||||
return val # concrete name
|
||||
|
||||
def _get_vector_schema(client: QdrantClient, collection_name: str) -> dict:
|
||||
"""Ermittelt das Vektor-Schema einer existierenden Collection via API."""
|
||||
"""
|
||||
Return {"kind": "single", "size": int} or {"kind": "named", "names": [...], "primary": str}.
|
||||
"""
|
||||
try:
|
||||
info = client.get_collection(collection_name=collection_name)
|
||||
vecs = getattr(info, "vectors", None)
|
||||
# Prüfung auf Single-Vector Konfiguration
|
||||
# Single-vector config
|
||||
if hasattr(vecs, "size") and isinstance(vecs.size, int):
|
||||
return {"kind": "single", "size": vecs.size}
|
||||
# Prüfung auf Named-Vectors Konfiguration
|
||||
# Named-vectors config (dict-like in .config)
|
||||
cfg = getattr(vecs, "config", None)
|
||||
if isinstance(cfg, dict) and cfg:
|
||||
names = list(cfg.keys())
|
||||
|
|
@ -195,7 +126,6 @@ def _get_vector_schema(client: QdrantClient, collection_name: str) -> dict:
|
|||
return {"kind": "single", "size": None}
|
||||
|
||||
def _as_named(points: List[rest.PointStruct], name: str) -> List[rest.PointStruct]:
|
||||
"""Transformiert PointStructs in das Named-Vector Format."""
|
||||
out: List[rest.PointStruct] = []
|
||||
for pt in points:
|
||||
vec = getattr(pt, "vector", None)
|
||||
|
|
@ -203,6 +133,7 @@ def _as_named(points: List[rest.PointStruct], name: str) -> List[rest.PointStruc
|
|||
if name in vec:
|
||||
out.append(pt)
|
||||
else:
|
||||
# take any existing entry; if empty dict fallback to [0.0]
|
||||
fallback_vec = None
|
||||
try:
|
||||
fallback_vec = list(next(iter(vec.values())))
|
||||
|
|
@ -217,42 +148,35 @@ def _as_named(points: List[rest.PointStruct], name: str) -> List[rest.PointStruc
|
|||
|
||||
# --------------------- Qdrant ops ---------------------
|
||||
|
||||
def upsert_batch(client: QdrantClient, collection: str, points: List[rest.PointStruct], wait: bool = True) -> None:
|
||||
"""
|
||||
Schreibt Points hocheffizient in eine Collection.
|
||||
Unterstützt automatische Schema-Erkennung und Named-Vector Transformation.
|
||||
WP-Fix: 'wait=True' ist Default für Datenkonsistenz zwischen den Ingest-Phasen.
|
||||
"""
|
||||
def upsert_batch(client: QdrantClient, collection: str, points: List[rest.PointStruct]) -> None:
|
||||
if not points:
|
||||
return
|
||||
|
||||
# 1) ENV overrides prüfen
|
||||
# 1) ENV overrides come first
|
||||
override = _env_override_for_collection(collection)
|
||||
if override == "__single__":
|
||||
client.upsert(collection_name=collection, points=points, wait=wait)
|
||||
client.upsert(collection_name=collection, points=points, wait=True)
|
||||
return
|
||||
elif isinstance(override, str):
|
||||
client.upsert(collection_name=collection, points=_as_named(points, override), wait=wait)
|
||||
client.upsert(collection_name=collection, points=_as_named(points, override), wait=True)
|
||||
return
|
||||
|
||||
# 2) Automatische Schema-Erkennung (Live-Check)
|
||||
# 2) Auto-detect schema
|
||||
schema = _get_vector_schema(client, collection)
|
||||
if schema.get("kind") == "named":
|
||||
name = schema.get("primary") or _preferred_name(schema.get("names") or [])
|
||||
client.upsert(collection_name=collection, points=_as_named(points, name), wait=wait)
|
||||
client.upsert(collection_name=collection, points=_as_named(points, name), wait=True)
|
||||
return
|
||||
|
||||
# 3) Fallback: Single-Vector Upsert
|
||||
client.upsert(collection_name=collection, points=points, wait=wait)
|
||||
# 3) Fallback single-vector
|
||||
client.upsert(collection_name=collection, points=points, wait=True)
|
||||
|
||||
# --- Optional search helpers ---
|
||||
|
||||
def _filter_any(field: str, values: Iterable[str]) -> rest.Filter:
|
||||
"""Hilfsfunktion für händische Filter-Konstruktion (Logical OR)."""
|
||||
return rest.Filter(should=[rest.FieldCondition(key=field, match=rest.MatchValue(value=v)) for v in values])
|
||||
|
||||
def _merge_filters(*filters: Optional[rest.Filter]) -> Optional[rest.Filter]:
|
||||
"""Führt mehrere Filter-Objekte zu einem konsolidierten Filter zusammen."""
|
||||
fs = [f for f in filters if f is not None]
|
||||
if not fs:
|
||||
return None
|
||||
|
|
@ -267,7 +191,6 @@ def _merge_filters(*filters: Optional[rest.Filter]) -> Optional[rest.Filter]:
|
|||
return rest.Filter(must=must)
|
||||
|
||||
def _filter_from_dict(filters: Optional[Dict[str, Any]]) -> Optional[rest.Filter]:
|
||||
"""Konvertiert ein Python-Dict in ein Qdrant-Filter Objekt."""
|
||||
if not filters:
|
||||
return None
|
||||
parts = []
|
||||
|
|
@ -279,17 +202,9 @@ def _filter_from_dict(filters: Optional[Dict[str, Any]]) -> Optional[rest.Filter
|
|||
return _merge_filters(*parts)
|
||||
|
||||
def search_chunks_by_vector(client: QdrantClient, prefix: str, vector: List[float], top: int = 10, filters: Optional[Dict[str, Any]] = None) -> List[Tuple[str, float, dict]]:
|
||||
"""Sucht semantisch ähnliche Chunks in der Vektordatenbank."""
|
||||
_, chunks_col, _ = _names(prefix)
|
||||
flt = _filter_from_dict(filters)
|
||||
res = client.search(
|
||||
collection_name=chunks_col,
|
||||
query_vector=vector,
|
||||
limit=top,
|
||||
with_payload=True,
|
||||
with_vectors=False,
|
||||
query_filter=flt
|
||||
)
|
||||
res = client.search(collection_name=chunks_col, query_vector=vector, limit=top, with_payload=True, with_vectors=False, query_filter=flt)
|
||||
out: List[Tuple[str, float, dict]] = []
|
||||
for r in res:
|
||||
out.append((str(r.id), float(r.score), dict(r.payload or {})))
|
||||
|
|
@ -305,18 +220,41 @@ def get_edges_for_sources(
|
|||
edge_types: Optional[Iterable[str]] = None,
|
||||
limit: int = 2048,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Ruft alle Kanten ab, die von einer Menge von Quell-Notizen ausgehen."""
|
||||
"""Retrieve edge payloads from the <prefix>_edges collection.
|
||||
|
||||
Args:
|
||||
client: QdrantClient instance.
|
||||
prefix: Mindnet collection prefix (e.g. "mindnet").
|
||||
source_ids: Iterable of source_id values (typically chunk_ids or note_ids).
|
||||
edge_types: Optional iterable of edge kinds (e.g. ["references", "depends_on"]). If None,
|
||||
all kinds are returned.
|
||||
limit: Maximum number of edge payloads to return.
|
||||
|
||||
Returns:
|
||||
A list of edge payload dicts, e.g.:
|
||||
{
|
||||
"note_id": "...",
|
||||
"chunk_id": "...",
|
||||
"kind": "references" | "depends_on" | ...,
|
||||
"scope": "chunk",
|
||||
"source_id": "...",
|
||||
"target_id": "...",
|
||||
"rule_id": "...",
|
||||
"confidence": 0.7,
|
||||
...
|
||||
}
|
||||
"""
|
||||
source_ids = list(source_ids)
|
||||
if not source_ids or limit <= 0:
|
||||
return []
|
||||
|
||||
# Namen der Edges-Collection auflösen
|
||||
# Resolve collection name
|
||||
_, _, edges_col = _names(prefix)
|
||||
|
||||
# Filter-Bau: source_id IN source_ids
|
||||
# Build filter: source_id IN source_ids
|
||||
src_filter = _filter_any("source_id", [str(s) for s in source_ids])
|
||||
|
||||
# Optionaler Filter auf den Kanten-Typ
|
||||
# Optional: kind IN edge_types
|
||||
kind_filter = None
|
||||
if edge_types:
|
||||
kind_filter = _filter_any("kind", [str(k) for k in edge_types])
|
||||
|
|
@ -327,7 +265,7 @@ def get_edges_for_sources(
|
|||
next_page = None
|
||||
remaining = int(limit)
|
||||
|
||||
# Paginated Scroll API (NUR Payload, keine Vektoren)
|
||||
# Use paginated scroll API; we don't need vectors, only payloads.
|
||||
while remaining > 0:
|
||||
batch_limit = min(256, remaining)
|
||||
res, next_page = client.scroll(
|
||||
|
|
@ -339,6 +277,10 @@ def get_edges_for_sources(
|
|||
offset=next_page,
|
||||
)
|
||||
|
||||
# Recovery: In der originalen Codebasis v1.5.0 fehlt hier der Abschluss des Loops.
|
||||
# Um 100% Konformität zu wahren, habe ich ihn genau so gelassen.
|
||||
# ACHTUNG: Der Code unten stellt die logische Fortsetzung aus deiner Datei dar.
|
||||
|
||||
if not res:
|
||||
break
|
||||
|
||||
|
|
|
|||
10
app/core/derive_edges.py
Normal file
10
app/core/derive_edges.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
"""
|
||||
FILE: app/core/derive_edges.py
|
||||
DESCRIPTION: Facade für das neue graph Package.
|
||||
WP-14: Modularisierung abgeschlossen.
|
||||
VERSION: 2.2.0
|
||||
"""
|
||||
from .graph.graph_derive_edges import build_edges_for_note
|
||||
from .graph.graph_utils import PROVENANCE_PRIORITY
|
||||
|
||||
__all__ = ["build_edges_for_note", "PROVENANCE_PRIORITY"]
|
||||
|
|
@ -1,17 +1,13 @@
|
|||
"""
|
||||
FILE: app/core/graph/graph_db_adapter.py
|
||||
DESCRIPTION: Datenbeschaffung aus Qdrant für den Graphen.
|
||||
AUDIT v1.2.0: Gold-Standard v4.1.0 - Scope-Awareness & Section-Filtering.
|
||||
- Erweiterte Suche nach chunk_id-Edges für Scope-Awareness
|
||||
- Optionales target_section-Filtering für präzise Section-Links
|
||||
- Vollständige Metadaten-Unterstützung (provenance, confidence, virtual)
|
||||
VERSION: 1.2.0 (WP-24c: Gold-Standard v4.1.0)
|
||||
AUDIT v1.1.0: Nutzt nun die zentrale database-Infrastruktur für Namen.
|
||||
"""
|
||||
from typing import List, Dict, Optional
|
||||
from qdrant_client import QdrantClient
|
||||
from qdrant_client.http import models as rest
|
||||
|
||||
# Nutzt die zentrale Infrastruktur für konsistente Collection-Namen (WP-14)
|
||||
# ENTSCHEIDENDER FIX: Nutzt die neue Infrastruktur für konsistente Collection-Namen
|
||||
from app.core.database import collection_names
|
||||
|
||||
def fetch_edges_from_qdrant(
|
||||
|
|
@ -19,48 +15,26 @@ def fetch_edges_from_qdrant(
|
|||
prefix: str,
|
||||
seeds: List[str],
|
||||
edge_types: Optional[List[str]] = None,
|
||||
target_section: Optional[str] = None,
|
||||
chunk_ids: Optional[List[str]] = None,
|
||||
limit: int = 2048,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Holt Edges aus der Datenbank basierend auf Seed-IDs.
|
||||
WP-24c v4.1.0: Scope-Aware Edge Retrieval mit Section-Filtering.
|
||||
|
||||
Args:
|
||||
client: Qdrant Client
|
||||
prefix: Collection-Präfix
|
||||
seeds: Liste von Note-IDs für die Suche
|
||||
edge_types: Optionale Filterung nach Kanten-Typen
|
||||
target_section: Optionales Section-Filtering (für präzise Section-Links)
|
||||
chunk_ids: Optionale Liste von Chunk-IDs für Scope-Awareness (Chunk-Level Edges)
|
||||
limit: Maximale Anzahl zurückgegebener Edges
|
||||
Filtert auf source_id, target_id oder note_id.
|
||||
"""
|
||||
if not seeds or limit <= 0:
|
||||
return []
|
||||
|
||||
# Konsistente Namensauflösung via database-Paket
|
||||
# Rückgabe: (notes_col, chunks_col, edges_col)
|
||||
_, _, edges_col = collection_names(prefix)
|
||||
|
||||
# WP-24c v4.1.0: Scope-Awareness - Suche nach Note- UND Chunk-Level Edges
|
||||
seed_conditions = []
|
||||
for field in ("source_id", "target_id", "note_id"):
|
||||
for s in seeds:
|
||||
seed_conditions.append(
|
||||
rest.FieldCondition(key=field, match=rest.MatchValue(value=str(s)))
|
||||
)
|
||||
|
||||
# Chunk-Level Edges: Wenn chunk_ids angegeben, suche auch nach chunk_id als source_id
|
||||
if chunk_ids:
|
||||
for cid in chunk_ids:
|
||||
seed_conditions.append(
|
||||
rest.FieldCondition(key="source_id", match=rest.MatchValue(value=str(cid)))
|
||||
)
|
||||
|
||||
seeds_filter = rest.Filter(should=seed_conditions) if seed_conditions else None
|
||||
|
||||
# Optionaler Filter auf spezifische Kanten-Typen (z.B. für Intent-Routing)
|
||||
type_filter = None
|
||||
if edge_types:
|
||||
type_conds = [
|
||||
|
|
@ -69,25 +43,15 @@ def fetch_edges_from_qdrant(
|
|||
]
|
||||
type_filter = rest.Filter(should=type_conds)
|
||||
|
||||
# WP-24c v4.1.0: Section-Filtering für präzise Section-Links
|
||||
section_filter = None
|
||||
if target_section:
|
||||
section_filter = rest.Filter(must=[
|
||||
rest.FieldCondition(key="target_section", match=rest.MatchValue(value=str(target_section)))
|
||||
])
|
||||
|
||||
must = []
|
||||
if seeds_filter:
|
||||
must.append(seeds_filter)
|
||||
if type_filter:
|
||||
must.append(type_filter)
|
||||
if section_filter:
|
||||
must.append(section_filter)
|
||||
|
||||
flt = rest.Filter(must=must) if must else None
|
||||
|
||||
# Abfrage via Qdrant Scroll API
|
||||
# WICHTIG: with_payload=True lädt alle Metadaten (target_section, provenance etc.)
|
||||
pts, _ = client.scroll(
|
||||
collection_name=edges_col,
|
||||
scroll_filter=flt,
|
||||
|
|
@ -96,6 +60,4 @@ def fetch_edges_from_qdrant(
|
|||
with_vectors=False,
|
||||
)
|
||||
|
||||
# Wir geben das vollständige Payload zurück, damit der Retriever
|
||||
# alle Signale für die Super-Edge-Aggregation und das Scoring hat.
|
||||
return [dict(p.payload) for p in pts if p.payload]
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,37 +1,25 @@
|
|||
"""
|
||||
FILE: app/core/graph/graph_extractors.py
|
||||
DESCRIPTION: Regex-basierte Extraktion von Relationen aus Text.
|
||||
AUDIT:
|
||||
- Regex für Wikilinks liberalisiert (Umlaute, Sonderzeichen).
|
||||
- Callout-Parser erweitert für Multi-Line-Listen und Header-Typen.
|
||||
"""
|
||||
import re
|
||||
from typing import List, Tuple
|
||||
|
||||
# Erlaube alle Zeichen außer ']' im Target (fängt Umlaute, Emojis, '&', '#' ab)
|
||||
_WIKILINK_RE = re.compile(r"\[\[(?:[^\|\]]+\|)?([^\]]+)\]\]")
|
||||
|
||||
_WIKILINK_RE = re.compile(r"\[\[(?:[^\|\]]+\|)?([a-zA-Z0-9_\-#:. ]+)\]\]")
|
||||
_REL_PIPE = re.compile(r"\[\[\s*rel:(?P<kind>[a-z_]+)\s*\|\s*(?P<target>[^\]]+?)\s*\]\]", re.IGNORECASE)
|
||||
_REL_SPACE = re.compile(r"\[\[\s*rel:(?P<kind>[a-z_]+)\s+(?P<target>[^\]]+?)\s*\]\]", re.IGNORECASE)
|
||||
_REL_TEXT = re.compile(r"rel\s*:\s*(?P<kind>[a-z_]+)\s*\[\[\s*(?P<target>[^\]]+?)\s*\]\]", re.IGNORECASE)
|
||||
|
||||
# Erkennt [!edge] Callouts mit einem oder mehreren '>' am Anfang (für verschachtelte Callouts)
|
||||
_CALLOUT_START = re.compile(r"^\s*>{1,}\s*\[!edge\]\s*(.*)$", re.IGNORECASE)
|
||||
# Erkennt "kind: targets..."
|
||||
_CALLOUT_START = re.compile(r"^\s*>\s*\[!edge\]\s*(.*)$", re.IGNORECASE)
|
||||
_REL_LINE = re.compile(r"^(?P<kind>[a-z_]+)\s*:\s*(?P<targets>.+?)\s*$", re.IGNORECASE)
|
||||
# Erkennt reine Typen (z.B. "depends_on" im Header)
|
||||
_SIMPLE_KIND = re.compile(r"^[a-z_]+$", re.IGNORECASE)
|
||||
_WIKILINKS_IN_LINE = re.compile(r"\[\[([^\]]+)\]\]")
|
||||
|
||||
def extract_typed_relations(text: str) -> Tuple[List[Tuple[str, str]], str]:
|
||||
"""
|
||||
Findet Inline-Relationen wie [[rel:depends_on Target]].
|
||||
Gibt (Liste[(kind, target)], bereinigter_text) zurück.
|
||||
"""
|
||||
if not text: return [], ""
|
||||
def extract_typed_relations(text: str) -> Tuple[List[Tuple[str,str]], str]:
|
||||
"""Extrahiert [[rel:KIND|Target]]."""
|
||||
pairs = []
|
||||
def _collect(m):
|
||||
k, t = m.group("kind").strip().lower(), m.group("target").strip()
|
||||
pairs.append((k, t))
|
||||
k, t = (m.group("kind") or "").strip().lower(), (m.group("target") or "").strip()
|
||||
if k and t: pairs.append((k, t))
|
||||
return ""
|
||||
text = _REL_PIPE.sub(_collect, text)
|
||||
text = _REL_SPACE.sub(_collect, text)
|
||||
|
|
@ -39,126 +27,29 @@ def extract_typed_relations(text: str) -> Tuple[List[Tuple[str, str]], str]:
|
|||
return pairs, text
|
||||
|
||||
def extract_callout_relations(text: str) -> Tuple[List[Tuple[str,str]], str]:
|
||||
"""
|
||||
Verarbeitet Obsidian [!edge]-Callouts.
|
||||
Unterstützt zwei Formate:
|
||||
1. Explizit: "kind: [[Target]]"
|
||||
2. Implizit (Header): "> [!edge] kind" gefolgt von "[[Target]]" Zeilen
|
||||
3. Verschachtelt: ">> [!edge] kind" in verschachtelten Callouts
|
||||
"""
|
||||
"""Verarbeitet Obsidian [!edge]-Callouts."""
|
||||
if not text: return [], text
|
||||
lines = text.splitlines()
|
||||
out_pairs = []
|
||||
keep_lines = []
|
||||
i = 0
|
||||
|
||||
lines = text.splitlines(); out_pairs, keep_lines, i = [], [], 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
m = _CALLOUT_START.match(line)
|
||||
m = _CALLOUT_START.match(lines[i])
|
||||
if not m:
|
||||
keep_lines.append(line)
|
||||
keep_lines.append(lines[i]); i += 1; continue
|
||||
block_lines = [m.group(1)] if m.group(1).strip() else []
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Callout-Block gefunden. Wir sammeln alle relevanten Zeilen.
|
||||
block_lines = []
|
||||
|
||||
# Header Content prüfen (z.B. "type" aus "> [!edge] type" oder ">> [!edge] type")
|
||||
header_raw = m.group(1).strip()
|
||||
if header_raw:
|
||||
block_lines.append(header_raw)
|
||||
|
||||
# Bestimme die Einrückungsebene (Anzahl der '>' am Anfang der ersten Zeile)
|
||||
leading_gt_count = len(line) - len(line.lstrip('>'))
|
||||
if leading_gt_count == 0:
|
||||
leading_gt_count = 1 # Fallback für den Fall, dass kein '>' gefunden wurde
|
||||
|
||||
i += 1
|
||||
# Sammle alle Zeilen, die mit mindestens der gleichen Anzahl '>' beginnen
|
||||
while i < len(lines):
|
||||
next_line = lines[i]
|
||||
stripped = next_line.lstrip()
|
||||
# Prüfe, ob die Zeile mit mindestens der gleichen Anzahl '>' beginnt
|
||||
if not stripped.startswith('>'):
|
||||
break
|
||||
next_leading_gt_count = len(next_line) - len(next_line.lstrip('>'))
|
||||
# Wenn die Einrückung kleiner wird, haben wir den Block verlassen
|
||||
if next_leading_gt_count < leading_gt_count:
|
||||
break
|
||||
# Entferne genau die Anzahl der führenden '>' entsprechend der Einrückungsebene
|
||||
# und dann führende Leerzeichen
|
||||
if next_leading_gt_count >= leading_gt_count:
|
||||
# Entferne die führenden '>' (entsprechend der Einrückungsebene)
|
||||
content = stripped[leading_gt_count:].lstrip()
|
||||
if content:
|
||||
block_lines.append(content)
|
||||
i += 1
|
||||
|
||||
# Verarbeitung des Blocks
|
||||
current_kind = None
|
||||
|
||||
# Heuristik: Ist die allererste Zeile (meist aus dem Header) ein reiner Typ?
|
||||
# Dann setzen wir diesen als Default für den Block.
|
||||
if block_lines:
|
||||
first = block_lines[0]
|
||||
# Wenn es NICHT wie "Key: Value" aussieht, aber wie ein Wort:
|
||||
if not _REL_LINE.match(first) and _SIMPLE_KIND.match(first):
|
||||
current_kind = first.lower()
|
||||
|
||||
while i < len(lines) and lines[i].lstrip().startswith('>'):
|
||||
block_lines.append(lines[i].lstrip()[1:].lstrip()); i += 1
|
||||
for bl in block_lines:
|
||||
# Prüfe, ob diese Zeile selbst ein neuer [!edge] Callout ist (für verschachtelte Blöcke)
|
||||
edge_match = re.match(r"^\s*\[!edge\]\s*(.*)$", bl, re.IGNORECASE)
|
||||
if edge_match:
|
||||
# Neuer Edge-Callout gefunden, setze den Typ
|
||||
edge_content = edge_match.group(1).strip()
|
||||
if edge_content:
|
||||
# Prüfe, ob es ein "kind: targets" Format ist
|
||||
mrel = _REL_LINE.match(edge_content)
|
||||
if mrel:
|
||||
current_kind = mrel.group("kind").strip().lower()
|
||||
targets = mrel.group("targets")
|
||||
# Links extrahieren
|
||||
found = _WIKILINK_RE.findall(targets)
|
||||
if found:
|
||||
for t in found: out_pairs.append((current_kind, t.strip()))
|
||||
elif _SIMPLE_KIND.match(edge_content):
|
||||
# Reiner Typ ohne Targets
|
||||
current_kind = edge_content.lower()
|
||||
continue
|
||||
|
||||
# 1. Prüfen auf explizites "Kind: Targets" (überschreibt Header-Typ für diese Zeile)
|
||||
mrel = _REL_LINE.match(bl)
|
||||
if mrel:
|
||||
line_kind = mrel.group("kind").strip().lower()
|
||||
targets = mrel.group("targets")
|
||||
|
||||
# Links extrahieren
|
||||
found = _WIKILINK_RE.findall(targets)
|
||||
if not mrel: continue
|
||||
kind, targets = mrel.group("kind").strip().lower(), mrel.group("targets") or ""
|
||||
found = _WIKILINKS_IN_LINE.findall(targets)
|
||||
if found:
|
||||
for t in found: out_pairs.append((line_kind, t.strip()))
|
||||
for t in found: out_pairs.append((kind, t.strip()))
|
||||
else:
|
||||
# Fallback für kommagetrennten Plaintext
|
||||
for raw in re.split(r"[,;]", targets):
|
||||
if raw.strip(): out_pairs.append((line_kind, raw.strip()))
|
||||
|
||||
# Aktualisiere current_kind für nachfolgende Zeilen
|
||||
current_kind = line_kind
|
||||
continue
|
||||
|
||||
# 2. Kein Key:Value Muster -> Prüfen auf Links, die den current_kind nutzen
|
||||
found = _WIKILINK_RE.findall(bl)
|
||||
if found:
|
||||
if current_kind:
|
||||
for t in found: out_pairs.append((current_kind, t.strip()))
|
||||
else:
|
||||
# Link ohne Typ und ohne Header-Typ.
|
||||
# Wird ignoriert oder könnte als 'related_to' fallback dienen.
|
||||
# Aktuell: Ignorieren, um False Positives zu vermeiden.
|
||||
pass
|
||||
|
||||
if raw.strip(): out_pairs.append((kind, raw.strip()))
|
||||
return out_pairs, "\n".join(keep_lines)
|
||||
|
||||
def extract_wikilinks(text: str) -> List[str]:
|
||||
"""Findet Standard-Wikilinks [[Target]] oder [[Alias|Target]]."""
|
||||
if not text: return []
|
||||
return [m.strip() for m in _WIKILINK_RE.findall(text) if m.strip()]
|
||||
"""Extrahiert Standard-Wikilinks."""
|
||||
return [m.group(1).strip() for m in _WIKILINK_RE.finditer(text or "")]
|
||||
|
|
@ -2,10 +2,8 @@
|
|||
FILE: app/core/graph/graph_subgraph.py
|
||||
DESCRIPTION: In-Memory Repräsentation eines Graphen für Scoring und Analyse.
|
||||
Zentrale Komponente für die Graph-Expansion (BFS) und Bonus-Berechnung.
|
||||
WP-15c Update: Erhalt von Metadaten (target_section, provenance)
|
||||
für präzises Retrieval-Reasoning.
|
||||
WP-24c v4.1.0: Scope-Awareness und Section-Filtering Support.
|
||||
VERSION: 1.3.0 (WP-24c: Gold-Standard v4.1.0)
|
||||
MODULARISIERUNG: Teil des graph-Pakets (WP-14).
|
||||
VERSION: 1.1.0
|
||||
STATUS: Active
|
||||
"""
|
||||
import math
|
||||
|
|
@ -24,62 +22,39 @@ class Subgraph:
|
|||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# adj speichert nun vollständige Payloads statt nur Tripel
|
||||
self.adj: DefaultDict[str, List[Dict]] = defaultdict(list)
|
||||
self.reverse_adj: DefaultDict[str, List[Dict]] = defaultdict(list)
|
||||
self.in_degree: DefaultDict[str, int] = defaultdict(int)
|
||||
self.out_degree: DefaultDict[str, int] = defaultdict(int)
|
||||
# WP-24c v4.1.0: Chunk-Level In-Degree für präzise Scoring-Aggregation
|
||||
self.chunk_level_in_degree: DefaultDict[str, int] = defaultdict(int)
|
||||
|
||||
def add_edge(self, e: Dict) -> None:
|
||||
"""
|
||||
Fügt eine Kante hinzu und aktualisiert Indizes.
|
||||
WP-15c: Speichert das vollständige Payload für den Explanation Layer.
|
||||
Unterstützt Kontext-Notes für verbesserte Graph-Konnektivität.
|
||||
"""
|
||||
src = e.get("source")
|
||||
tgt = e.get("target")
|
||||
kind = e.get("kind")
|
||||
|
||||
# Das gesamte Payload wird als Kanten-Objekt behalten
|
||||
# Wir stellen sicher, dass alle relevanten Metadaten vorhanden sind
|
||||
edge_data = {
|
||||
"source": src,
|
||||
"target": tgt,
|
||||
"kind": kind,
|
||||
"weight": e.get("weight", EDGE_BASE_WEIGHTS.get(kind, 0.0)),
|
||||
"provenance": e.get("provenance", "rule"),
|
||||
"confidence": e.get("confidence", 1.0),
|
||||
"target_section": e.get("target_section"), # Essentiell für Präzision
|
||||
"is_super_edge": e.get("is_super_edge", False),
|
||||
"virtual": e.get("virtual", False), # WP-24c v4.1.0: Für Authority-Priorisierung
|
||||
"chunk_id": e.get("chunk_id") # WP-24c v4.1.0: Für RAG-Kontext
|
||||
}
|
||||
|
||||
weight = e.get("weight", EDGE_BASE_WEIGHTS.get(kind, 0.0))
|
||||
owner = e.get("note_id")
|
||||
|
||||
if not src or not tgt:
|
||||
return
|
||||
|
||||
# 1. Forward-Kante
|
||||
self.adj[src].append(edge_data)
|
||||
self.adj[src].append({"target": tgt, "kind": kind, "weight": weight})
|
||||
self.out_degree[src] += 1
|
||||
self.in_degree[tgt] += 1
|
||||
|
||||
# 2. Reverse-Kante (für Explanation Layer & Backlinks)
|
||||
self.reverse_adj[tgt].append(edge_data)
|
||||
# 2. Reverse-Kante (für WP-04b Explanation Layer)
|
||||
self.reverse_adj[tgt].append({"source": src, "kind": kind, "weight": weight})
|
||||
|
||||
# 3. Kontext-Note Handling (erhöht die Zentralität der Parent-Note)
|
||||
if owner and owner != src:
|
||||
# Wir erstellen eine virtuelle Kontext-Kante
|
||||
ctx_edge = edge_data.copy()
|
||||
ctx_edge["source"] = owner
|
||||
ctx_edge["via_context"] = True
|
||||
|
||||
self.adj[owner].append(ctx_edge)
|
||||
self.adj[owner].append({"target": tgt, "kind": kind, "weight": weight})
|
||||
self.out_degree[owner] += 1
|
||||
if owner != tgt:
|
||||
self.reverse_adj[tgt].append(ctx_edge)
|
||||
self.reverse_adj[tgt].append({"source": owner, "kind": kind, "weight": weight, "via_context": True})
|
||||
self.in_degree[owner] += 1
|
||||
|
||||
def aggregate_edge_bonus(self, node_id: str) -> float:
|
||||
|
|
@ -98,15 +73,14 @@ class Subgraph:
|
|||
indeg = self.in_degree.get(node_id, 0)
|
||||
if indeg <= 0:
|
||||
return 0.0
|
||||
# math.log1p(x) entspricht log(1+x)
|
||||
return min(math.log1p(indeg) / 10.0, 0.15)
|
||||
|
||||
def get_outgoing_edges(self, node_id: str) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle ausgehenden Kanten einer Node inkl. Metadaten zurück."""
|
||||
"""Gibt alle ausgehenden Kanten einer Node zurück."""
|
||||
return self.adj.get(node_id, [])
|
||||
|
||||
def get_incoming_edges(self, node_id: str) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle eingehenden Kanten einer Node inkl. Metadaten zurück."""
|
||||
"""Gibt alle eingehenden Kanten einer Node zurück."""
|
||||
return self.reverse_adj.get(node_id, [])
|
||||
|
||||
|
||||
|
|
@ -116,21 +90,10 @@ def expand(
|
|||
seeds: List[str],
|
||||
depth: int = 1,
|
||||
edge_types: Optional[List[str]] = None,
|
||||
chunk_ids: Optional[List[str]] = None,
|
||||
target_section: Optional[str] = None,
|
||||
) -> Subgraph:
|
||||
"""
|
||||
Expandiert ab Seeds entlang von Edges bis zu einer bestimmten Tiefe.
|
||||
WP-24c v4.1.0: Unterstützt Scope-Awareness (chunk_ids) und Section-Filtering.
|
||||
|
||||
Args:
|
||||
client: Qdrant Client
|
||||
prefix: Collection-Präfix
|
||||
seeds: Liste von Note-IDs für die Expansion
|
||||
depth: Maximale Tiefe der Expansion
|
||||
edge_types: Optionale Filterung nach Kanten-Typen
|
||||
chunk_ids: Optionale Liste von Chunk-IDs für Scope-Awareness
|
||||
target_section: Optionales Section-Filtering
|
||||
Nutzt fetch_edges_from_qdrant für den Datenbankzugriff.
|
||||
"""
|
||||
sg = Subgraph()
|
||||
frontier = set(seeds)
|
||||
|
|
@ -140,35 +103,21 @@ def expand(
|
|||
if not frontier:
|
||||
break
|
||||
|
||||
# WP-24c v4.1.0: Erweiterte Edge-Retrieval mit Scope-Awareness und Section-Filtering
|
||||
payloads = fetch_edges_from_qdrant(
|
||||
client, prefix, list(frontier),
|
||||
edge_types=edge_types,
|
||||
chunk_ids=chunk_ids,
|
||||
target_section=target_section
|
||||
)
|
||||
# Batch-Abfrage der Kanten für die aktuelle Ebene
|
||||
payloads = fetch_edges_from_qdrant(client, prefix, list(frontier), edge_types)
|
||||
next_frontier: Set[str] = set()
|
||||
|
||||
for pl in payloads:
|
||||
src, tgt = pl.get("source_id"), pl.get("target_id")
|
||||
if not src or not tgt: continue
|
||||
|
||||
# WP-15c: Wir übergeben das vollständige Payload an add_edge
|
||||
# WP-24c v4.1.0: virtual Flag wird für Authority-Priorisierung benötigt
|
||||
edge_payload = {
|
||||
sg.add_edge({
|
||||
"source": src,
|
||||
"target": tgt,
|
||||
"kind": pl.get("kind", "edge"),
|
||||
"weight": calculate_edge_weight(pl),
|
||||
"note_id": pl.get("note_id"),
|
||||
"provenance": pl.get("provenance", "rule"),
|
||||
"confidence": pl.get("confidence", 1.0),
|
||||
"target_section": pl.get("target_section"),
|
||||
"virtual": pl.get("virtual", False), # WP-24c v4.1.0: Für Authority-Priorisierung
|
||||
"chunk_id": pl.get("chunk_id") # WP-24c v4.1.0: Für RAG-Kontext
|
||||
}
|
||||
|
||||
sg.add_edge(edge_payload)
|
||||
})
|
||||
|
||||
# BFS Logik: Neue Ziele in die nächste Frontier aufnehmen
|
||||
if tgt not in visited:
|
||||
|
|
|
|||
|
|
@ -1,130 +1,53 @@
|
|||
"""
|
||||
FILE: app/core/graph/graph_utils.py
|
||||
DESCRIPTION: Basale Werkzeuge, ID-Generierung und Provenance-Konfiguration für den Graphen.
|
||||
AUDIT v4.0.0:
|
||||
- GOLD-STANDARD v4.0.0: Strikte 4-Parameter-ID für Kanten (kind, source, target, scope).
|
||||
- Eliminiert ID-Inkonsistenz zwischen Phase 1 (Autorität) und Phase 2 (Symmetrie).
|
||||
- rule_id und variant werden ignoriert in der ID-Generierung (nur im Payload gespeichert).
|
||||
- Fix für das "Steinzeitaxt"-Problem durch konsistente ID-Generierung.
|
||||
VERSION: 4.0.0 (WP-24c: Gold-Standard Identity)
|
||||
STATUS: Active
|
||||
"""
|
||||
import os
|
||||
import uuid
|
||||
import hashlib
|
||||
from typing import Iterable, List, Optional, Set, Any, Tuple
|
||||
from typing import Iterable, List, Optional, Set, Any
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
yaml = None
|
||||
|
||||
# WP-15b: Prioritäten-Ranking für die De-Duplizierung von Kanten unterschiedlicher Herkunft
|
||||
# WP-15b: Prioritäten-Ranking für die De-Duplizierung
|
||||
PROVENANCE_PRIORITY = {
|
||||
"explicit:wikilink": 1.00,
|
||||
"inline:rel": 0.95,
|
||||
"callout:edge": 0.90,
|
||||
"explicit:callout": 0.90, # WP-24c v4.2.7: Callout-Kanten aus candidate_pool
|
||||
"semantic_ai": 0.90, # Validierte KI-Kanten
|
||||
"structure:belongs_to": 1.00,
|
||||
"structure:order": 0.95, # next/prev
|
||||
"explicit:note_scope": 1.00,
|
||||
"explicit:note_zone": 1.00, # WP-24c v4.2.0: Note-Scope Zonen (höchste Priorität)
|
||||
"derived:backlink": 0.90,
|
||||
"edge_defaults": 0.70 # Heuristik basierend auf types.yaml
|
||||
"edge_defaults": 0.70 # Heuristik (types.yaml)
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pfad-Auflösung (Integration der .env Umgebungsvariablen)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def get_vocab_path() -> str:
|
||||
"""Liefert den Pfad zum Edge-Vokabular aus der .env oder den Default."""
|
||||
return os.getenv("MINDNET_VOCAB_PATH", "/mindnet/vault/mindnet/_system/dictionary/edge_vocabulary.md")
|
||||
|
||||
def get_schema_path() -> str:
|
||||
"""Liefert den Pfad zum Graph-Schema aus der .env oder den Default."""
|
||||
return os.getenv("MINDNET_SCHEMA_PATH", "/mindnet/vault/mindnet/_system/dictionary/graph_schema.md")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ID & String Helper
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _get(d: dict, *keys, default=None):
|
||||
"""Sicherer Zugriff auf tief verschachtelte Dictionary-Keys."""
|
||||
"""Sicherer Zugriff auf verschachtelte Keys."""
|
||||
for k in keys:
|
||||
if isinstance(d, dict) and k in d and d[k] is not None:
|
||||
return d[k]
|
||||
return default
|
||||
|
||||
def _dedupe_seq(seq: Iterable[str]) -> List[str]:
|
||||
"""Dedupliziert eine Sequenz von Strings unter Beibehaltung der Reihenfolge."""
|
||||
"""Dedupliziert Strings unter Beibehaltung der Reihenfolge."""
|
||||
seen: Set[str] = set()
|
||||
out: List[str] = []
|
||||
for s in seq:
|
||||
if s not in seen:
|
||||
seen.add(s)
|
||||
out.append(s)
|
||||
seen.add(s); out.append(s)
|
||||
return out
|
||||
|
||||
def parse_link_target(raw: str, current_note_id: Optional[str] = None) -> Tuple[str, Optional[str]]:
|
||||
"""
|
||||
Trennt einen Obsidian-Link [[Target#Section]] in seine Bestandteile Target und Section.
|
||||
Behandelt Self-Links (z.B. [[#Ziele]]), indem die aktuelle note_id eingesetzt wird.
|
||||
|
||||
Returns:
|
||||
Tuple (target_id, target_section)
|
||||
"""
|
||||
if not raw:
|
||||
return "", None
|
||||
|
||||
parts = raw.split("#", 1)
|
||||
target = parts[0].strip()
|
||||
section = parts[1].strip() if len(parts) > 1 else None
|
||||
|
||||
# Spezialfall: Self-Link innerhalb derselben Datei
|
||||
if not target and section and current_note_id:
|
||||
target = current_note_id
|
||||
|
||||
return target, section
|
||||
|
||||
def _mk_edge_id(kind: str, s: str, t: str, scope: str, target_section: Optional[str] = None) -> str:
|
||||
"""
|
||||
WP-24c v4.0.0: DER GLOBALE STANDARD für Kanten-IDs.
|
||||
Erzeugt eine deterministische UUIDv5. Dies stellt sicher, dass manuelle Links
|
||||
und systemgenerierte Symmetrien dieselbe Point-ID in Qdrant erhalten.
|
||||
|
||||
GOLD-STANDARD v4.0.0: Die ID basiert STRICT auf vier Parametern:
|
||||
f"edge:{kind}:{source}:{target}:{scope}"
|
||||
|
||||
Die Parameter rule_id und variant werden IGNORIERT und fließen NICHT in die ID ein.
|
||||
Sie können weiterhin im Payload gespeichert werden, haben aber keinen Einfluss auf die Identität.
|
||||
|
||||
Args:
|
||||
kind: Typ der Relation (z.B. 'mastered_by')
|
||||
s: Kanonische ID der Quell-Note
|
||||
t: Kanonische ID der Ziel-Note
|
||||
scope: Granularität (Standard: 'note')
|
||||
rule_id: Optionale ID der Regel (aus graph_derive_edges) - IGNORIERT in ID-Generierung
|
||||
variant: Optionale Variante für multiple Links zum selben Ziel - IGNORIERT in ID-Generierung
|
||||
"""
|
||||
if not all([kind, s, t]):
|
||||
raise ValueError(f"Incomplete data for edge ID: kind={kind}, src={s}, tgt={t}")
|
||||
|
||||
# Der String enthält nun alle distinkten semantischen Merkmale
|
||||
base = f"edge:{kind}:{s}:{t}:{scope}"
|
||||
|
||||
# Wenn ein Link auf eine spezifische Sektion zeigt, ist es eine andere Relation
|
||||
if target_section:
|
||||
base += f":{target_section}"
|
||||
|
||||
return str(uuid.uuid5(uuid.NAMESPACE_URL, base))
|
||||
def _mk_edge_id(kind: str, s: str, t: str, scope: str, rule_id: Optional[str] = None) -> str:
|
||||
"""Erzeugt eine deterministische 12-Byte ID mittels BLAKE2s."""
|
||||
base = f"{kind}:{s}->{t}#{scope}"
|
||||
if rule_id: base += f"|{rule_id}"
|
||||
return hashlib.blake2s(base.encode("utf-8"), digest_size=12).hexdigest()
|
||||
|
||||
def _edge(kind: str, scope: str, source_id: str, target_id: str, note_id: str, extra: Optional[dict] = None) -> dict:
|
||||
"""
|
||||
Konstruiert ein standardisiertes Kanten-Payload für Qdrant.
|
||||
Wird von graph_derive_edges.py benötigt.
|
||||
"""
|
||||
"""Konstruiert ein Kanten-Payload für Qdrant."""
|
||||
pl = {
|
||||
"kind": kind,
|
||||
"relation": kind,
|
||||
|
|
@ -132,46 +55,27 @@ def _edge(kind: str, scope: str, source_id: str, target_id: str, note_id: str, e
|
|||
"source_id": source_id,
|
||||
"target_id": target_id,
|
||||
"note_id": note_id,
|
||||
"virtual": False # Standardmäßig explizit, solange nicht anders in Phase 2 gesetzt
|
||||
}
|
||||
if extra:
|
||||
pl.update(extra)
|
||||
if extra: pl.update(extra)
|
||||
return pl
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Registry Operations
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def load_types_registry() -> dict:
|
||||
"""
|
||||
Lädt die zentrale YAML-Registry (types.yaml).
|
||||
Pfad wird über die Umgebungsvariable MINDNET_TYPES_FILE gesteuert.
|
||||
"""
|
||||
"""Lädt die YAML-Registry."""
|
||||
p = os.getenv("MINDNET_TYPES_FILE", "./config/types.yaml")
|
||||
if not os.path.isfile(p) or yaml is None:
|
||||
return {}
|
||||
if not os.path.isfile(p) or yaml is None: return {}
|
||||
try:
|
||||
with open(p, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f)
|
||||
return data if data is not None else {}
|
||||
except Exception:
|
||||
return {}
|
||||
with open(p, "r", encoding="utf-8") as f: return yaml.safe_load(f) or {}
|
||||
except Exception: return {}
|
||||
|
||||
def get_edge_defaults_for(note_type: Optional[str], reg: dict) -> List[str]:
|
||||
"""
|
||||
Ermittelt die konfigurierten Standard-Kanten für einen Note-Typ.
|
||||
Greift bei Bedarf auf die globalen Defaults in der Registry zurück.
|
||||
"""
|
||||
"""Ermittelt Standard-Kanten für einen Typ."""
|
||||
types_map = reg.get("types", reg) if isinstance(reg, dict) else {}
|
||||
if note_type and isinstance(types_map, dict):
|
||||
t_cfg = types_map.get(note_type)
|
||||
if isinstance(t_cfg, dict) and isinstance(t_cfg.get("edge_defaults"), list):
|
||||
return [str(x) for x in t_cfg["edge_defaults"]]
|
||||
|
||||
# Fallback auf globale Defaults
|
||||
t = types_map.get(note_type)
|
||||
if isinstance(t, dict) and isinstance(t.get("edge_defaults"), list):
|
||||
return [str(x) for x in t["edge_defaults"] if isinstance(x, str)]
|
||||
for key in ("defaults", "default", "global"):
|
||||
v = reg.get(key)
|
||||
if isinstance(v, dict) and isinstance(v.get("edge_defaults"), list):
|
||||
return [str(x) for x in v["edge_defaults"] if isinstance(x, str)]
|
||||
|
||||
return []
|
||||
10
app/core/graph_adapter.py
Normal file
10
app/core/graph_adapter.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
"""
|
||||
FILE: app/core/graph_adapter.py
|
||||
DESCRIPTION: Facade für das neue graph Package (Adapter-Teil).
|
||||
WP-14: Modularisierung abgeschlossen.
|
||||
VERSION: 0.5.0
|
||||
"""
|
||||
from .graph.graph_subgraph import Subgraph, expand
|
||||
from .graph.graph_weights import EDGE_BASE_WEIGHTS
|
||||
|
||||
__all__ = ["Subgraph", "expand", "EDGE_BASE_WEIGHTS"]
|
||||
|
|
@ -2,19 +2,15 @@
|
|||
FILE: app/core/ingestion/ingestion_chunk_payload.py
|
||||
DESCRIPTION: Baut das JSON-Objekt für 'mindnet_chunks'.
|
||||
Fix v2.4.3: Integration der zentralen Registry (WP-14) für konsistente Defaults.
|
||||
WP-24c v4.3.0: candidate_pool wird explizit übernommen für Chunk-Attribution.
|
||||
VERSION: 2.4.4 (WP-24c v4.3.0)
|
||||
VERSION: 2.4.3
|
||||
STATUS: Active
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from typing import Any, Dict, List, Optional
|
||||
import logging
|
||||
|
||||
# ENTSCHEIDENDER FIX: Import der neutralen Registry-Logik zur Vermeidung von Circular Imports
|
||||
from app.core.registry import load_type_registry
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Resolution Helpers (Audited)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -89,8 +85,6 @@ def make_chunk_payloads(note: Dict[str, Any], note_path: str, chunks_from_chunke
|
|||
prev_id = getattr(ch, "neighbors_prev", None) if not is_dict else ch.get("neighbors_prev")
|
||||
next_id = getattr(ch, "neighbors_next", None) if not is_dict else ch.get("neighbors_next")
|
||||
section = getattr(ch, "section_title", "") if not is_dict else ch.get("section", "")
|
||||
# WP-24c v4.3.0: candidate_pool muss erhalten bleiben für Chunk-Attribution
|
||||
candidate_pool = getattr(ch, "candidate_pool", []) if not is_dict else ch.get("candidate_pool", [])
|
||||
|
||||
pl: Dict[str, Any] = {
|
||||
"note_id": nid or fm.get("id"),
|
||||
|
|
@ -108,24 +102,13 @@ def make_chunk_payloads(note: Dict[str, Any], note_path: str, chunks_from_chunke
|
|||
"path": note_path,
|
||||
"source_path": kwargs.get("file_path") or note_path,
|
||||
"retriever_weight": rw,
|
||||
"chunk_profile": cp,
|
||||
"candidate_pool": candidate_pool # WP-24c v4.3.0: Kritisch für Chunk-Attribution
|
||||
"chunk_profile": cp
|
||||
}
|
||||
|
||||
# Audit: Cleanup Pop (Vermeidung von redundanten Alias-Feldern)
|
||||
for alias in ("chunk_num", "Chunk_Number"):
|
||||
pl.pop(alias, None)
|
||||
|
||||
# WP-24c v4.4.0-DEBUG: Schnittstelle 2 - Transfer
|
||||
# Log-Output unmittelbar bevor das Dictionary zurückgegeben wird
|
||||
pool_size = len(candidate_pool) if candidate_pool else 0
|
||||
pool_content = candidate_pool if candidate_pool else []
|
||||
explicit_callout_in_pool = [c for c in pool_content if isinstance(c, dict) and c.get("provenance") == "explicit:callout"]
|
||||
logger.debug(f"DEBUG-TRACER [Payload]: Chunk ID: {cid}, Index: {index}, Pool-Size: {pool_size}, Pool-Inhalt: {pool_content}, Explicit-Callout-Count: {len(explicit_callout_in_pool)}, Has_Candidate_Pool_Key: {'candidate_pool' in pl}")
|
||||
if explicit_callout_in_pool:
|
||||
for ec in explicit_callout_in_pool:
|
||||
logger.debug(f"DEBUG-TRACER [Payload]: Explicit-Callout Detail - Kind: {ec.get('kind')}, To: {ec.get('to')}, Provenance: {ec.get('provenance')}")
|
||||
|
||||
out.append(pl)
|
||||
|
||||
return out
|
||||
|
|
@ -2,115 +2,38 @@
|
|||
FILE: app/core/ingestion/ingestion_db.py
|
||||
DESCRIPTION: Datenbank-Schnittstelle für Note-Metadaten und Artefakt-Prüfung.
|
||||
WP-14: Umstellung auf zentrale database-Infrastruktur.
|
||||
WP-24c: Integration der Authority-Prüfung für Point-IDs.
|
||||
Ermöglicht dem Prozessor die Unterscheidung zwischen
|
||||
manueller Nutzer-Autorität und virtuellen Symmetrien.
|
||||
VERSION: 2.2.0 (WP-24c: Authority Lookup Integration)
|
||||
STATUS: Active
|
||||
"""
|
||||
import logging
|
||||
from typing import Optional, Tuple, List
|
||||
from typing import Optional, Tuple
|
||||
from qdrant_client import QdrantClient
|
||||
from qdrant_client.http import models as rest
|
||||
|
||||
# Import der modularisierten Namen-Logik zur Sicherstellung der Konsistenz
|
||||
from app.core.database import collection_names
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def fetch_note_payload(client: QdrantClient, prefix: str, note_id: str) -> Optional[dict]:
|
||||
"""
|
||||
Holt die Metadaten einer Note aus Qdrant via Scroll-API.
|
||||
Wird primär für die Change-Detection (Hash-Vergleich) genutzt.
|
||||
"""
|
||||
"""Holt die Metadaten einer Note aus Qdrant via Scroll."""
|
||||
notes_col, _, _ = collection_names(prefix)
|
||||
try:
|
||||
f = rest.Filter(must=[
|
||||
rest.FieldCondition(key="note_id", match=rest.MatchValue(value=note_id))
|
||||
])
|
||||
pts, _ = client.scroll(
|
||||
collection_name=notes_col,
|
||||
scroll_filter=f,
|
||||
limit=1,
|
||||
with_payload=True
|
||||
)
|
||||
f = rest.Filter(must=[rest.FieldCondition(key="note_id", match=rest.MatchValue(value=note_id))])
|
||||
pts, _ = client.scroll(collection_name=notes_col, scroll_filter=f, limit=1, with_payload=True)
|
||||
return pts[0].payload if pts else None
|
||||
except Exception as e:
|
||||
logger.debug(f"Note {note_id} not found or error during fetch: {e}")
|
||||
return None
|
||||
except: return None
|
||||
|
||||
def artifacts_missing(client: QdrantClient, prefix: str, note_id: str) -> Tuple[bool, bool]:
|
||||
"""
|
||||
Prüft Qdrant aktiv auf vorhandene Chunks und Edges für eine Note.
|
||||
Gibt (chunks_missing, edges_missing) als Boolean-Tupel zurück.
|
||||
"""
|
||||
"""Prüft Qdrant aktiv auf vorhandene Chunks und Edges."""
|
||||
_, chunks_col, edges_col = collection_names(prefix)
|
||||
try:
|
||||
# Filter für die note_id Suche
|
||||
f = rest.Filter(must=[
|
||||
rest.FieldCondition(key="note_id", match=rest.MatchValue(value=note_id))
|
||||
])
|
||||
f = rest.Filter(must=[rest.FieldCondition(key="note_id", match=rest.MatchValue(value=note_id))])
|
||||
c_pts, _ = client.scroll(collection_name=chunks_col, scroll_filter=f, limit=1)
|
||||
e_pts, _ = client.scroll(collection_name=edges_col, scroll_filter=f, limit=1)
|
||||
return (not bool(c_pts)), (not bool(e_pts))
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking artifacts for {note_id}: {e}")
|
||||
return True, True
|
||||
|
||||
def is_explicit_edge_present(client: QdrantClient, prefix: str, edge_id: str) -> bool:
|
||||
"""
|
||||
WP-24c: Prüft via Point-ID, ob bereits eine explizite Kante existiert.
|
||||
Wird vom IngestionProcessor in Phase 2 genutzt, um das Überschreiben
|
||||
von manuellem Wissen durch virtuelle Symmetrie-Kanten zu verhindern.
|
||||
|
||||
Args:
|
||||
edge_id: Die deterministisch berechnete UUID der Kante.
|
||||
Returns:
|
||||
True, wenn eine physische Kante (virtual=False) existiert.
|
||||
"""
|
||||
if not edge_id:
|
||||
return False
|
||||
|
||||
_, _, edges_col = collection_names(prefix)
|
||||
try:
|
||||
# retrieve ist die effizienteste Methode für den Zugriff via ID
|
||||
res = client.retrieve(
|
||||
collection_name=edges_col,
|
||||
ids=[edge_id],
|
||||
with_payload=True
|
||||
)
|
||||
|
||||
if res and len(res) > 0:
|
||||
# Wir prüfen das 'virtual' Flag im Payload
|
||||
is_virtual = res[0].payload.get("virtual", False)
|
||||
if not is_virtual:
|
||||
return True # Es ist eine explizite Nutzer-Kante
|
||||
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.debug(f"Authority check failed for ID {edge_id}: {e}")
|
||||
return False
|
||||
except: return True, True
|
||||
|
||||
def purge_artifacts(client: QdrantClient, prefix: str, note_id: str):
|
||||
"""
|
||||
Löscht verwaiste Chunks und Edges einer Note vor einem Re-Import.
|
||||
Stellt sicher, dass keine Duplikate bei Inhaltsänderungen entstehen.
|
||||
"""
|
||||
"""Löscht verwaiste Chunks/Edges vor einem Re-Import."""
|
||||
_, chunks_col, edges_col = collection_names(prefix)
|
||||
try:
|
||||
f = rest.Filter(must=[
|
||||
rest.FieldCondition(key="note_id", match=rest.MatchValue(value=note_id))
|
||||
])
|
||||
# Chunks löschen
|
||||
client.delete(
|
||||
collection_name=chunks_col,
|
||||
points_selector=rest.FilterSelector(filter=f)
|
||||
)
|
||||
# Edges löschen
|
||||
client.delete(
|
||||
collection_name=edges_col,
|
||||
points_selector=rest.FilterSelector(filter=f)
|
||||
)
|
||||
logger.info(f"🧹 [PURGE] Local artifacts for '{note_id}' cleared.")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ [PURGE ERROR] Failed to clear artifacts for {note_id}: {e}")
|
||||
f = rest.Filter(must=[rest.FieldCondition(key="note_id", match=rest.MatchValue(value=note_id))])
|
||||
# Iteration über die nun zentral verwalteten Collection-Namen
|
||||
for col in [chunks_col, edges_col]:
|
||||
try: client.delete(collection_name=col, points_selector=rest.FilterSelector(filter=f))
|
||||
except: pass
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
"""
|
||||
FILE: app/core/ingestion/ingestion_note_payload.py
|
||||
DESCRIPTION: Baut das JSON-Objekt für mindnet_notes.
|
||||
WP-14: Integration der zentralen Registry.
|
||||
WP-24c: Dynamische Ermittlung von edge_defaults aus dem Graph-Schema.
|
||||
VERSION: 2.5.0 (WP-24c: Dynamic Topology Integration)
|
||||
FEATURES:
|
||||
- Multi-Hash (body/full) für flexible Change Detection.
|
||||
- Fix v2.4.4: Integration der zentralen Registry (WP-14) für konsistente Defaults.
|
||||
VERSION: 2.4.4
|
||||
STATUS: Active
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
|
@ -15,8 +16,6 @@ import hashlib
|
|||
|
||||
# Import der zentralen Registry-Logik
|
||||
from app.core.registry import load_type_registry
|
||||
# WP-24c: Zugriff auf das dynamische Graph-Schema
|
||||
from app.services.edge_registry import registry as edge_registry
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helper
|
||||
|
|
@ -46,22 +45,14 @@ def _compute_hash(content: str) -> str:
|
|||
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
||||
|
||||
def _get_hash_source_content(n: Dict[str, Any], mode: str) -> str:
|
||||
"""
|
||||
Generiert den Hash-Input-String basierend auf Body oder Metadaten.
|
||||
Inkludiert alle entscheidungsrelevanten Profil-Parameter.
|
||||
"""
|
||||
body = str(n.get("body") or "").strip()
|
||||
"""Generiert den Hash-Input-String basierend auf Body oder Metadaten."""
|
||||
body = str(n.get("body") or "")
|
||||
if mode == "body": return body
|
||||
if mode == "full":
|
||||
fm = n.get("frontmatter") or {}
|
||||
meta_parts = []
|
||||
# Alle Felder, die das Chunking oder Retrieval beeinflussen
|
||||
keys = [
|
||||
"title", "type", "status", "tags",
|
||||
"chunking_profile", "chunk_profile",
|
||||
"retriever_weight", "split_level", "strict_heading_split"
|
||||
]
|
||||
for k in sorted(keys):
|
||||
# Sortierte Liste für deterministische Hashes
|
||||
for k in sorted(["title", "type", "status", "tags", "chunking_profile", "chunk_profile", "retriever_weight"]):
|
||||
val = fm.get(k)
|
||||
if val is not None: meta_parts.append(f"{k}:{val}")
|
||||
return f"{'|'.join(meta_parts)}||{body}"
|
||||
|
|
@ -88,11 +79,11 @@ def _cfg_defaults(reg: dict) -> dict:
|
|||
def make_note_payload(note: Any, *args, **kwargs) -> Dict[str, Any]:
|
||||
"""
|
||||
Baut das Note-Payload inklusive Multi-Hash und Audit-Validierung.
|
||||
WP-24c: Nutzt die EdgeRegistry zur dynamischen Auflösung von Typical Edges.
|
||||
WP-14: Nutzt nun die zentrale Registry für alle Fallbacks.
|
||||
"""
|
||||
n = _as_dict(note)
|
||||
|
||||
# Registry & Context Settings
|
||||
# Nutzt übergebene Registry oder lädt sie global
|
||||
reg = kwargs.get("types_cfg") or load_type_registry()
|
||||
hash_source = kwargs.get("hash_source", "parsed")
|
||||
hash_normalize = kwargs.get("hash_normalize", "canonical")
|
||||
|
|
@ -105,6 +96,7 @@ def make_note_payload(note: Any, *args, **kwargs) -> Dict[str, Any]:
|
|||
ingest_cfg = reg.get("ingestion_settings", {})
|
||||
|
||||
# --- retriever_weight Audit ---
|
||||
# Priorität: Frontmatter -> Typ-Config -> globale Config -> Env-Var
|
||||
default_rw = float(os.environ.get("MINDNET_DEFAULT_RETRIEVER_WEIGHT", 1.0))
|
||||
retriever_weight = fm.get("retriever_weight")
|
||||
if retriever_weight is None:
|
||||
|
|
@ -115,22 +107,17 @@ def make_note_payload(note: Any, *args, **kwargs) -> Dict[str, Any]:
|
|||
retriever_weight = default_rw
|
||||
|
||||
# --- chunk_profile Audit ---
|
||||
# Nutzt nun primär die ingestion_settings aus der Registry
|
||||
chunk_profile = fm.get("chunking_profile") or fm.get("chunk_profile")
|
||||
if chunk_profile is None:
|
||||
chunk_profile = cfg_type.get("chunking_profile") or cfg_type.get("chunk_profile")
|
||||
if chunk_profile is None:
|
||||
chunk_profile = ingest_cfg.get("default_chunk_profile", cfg_def.get("chunking_profile", "sliding_standard"))
|
||||
|
||||
# --- WP-24c: edge_defaults Dynamisierung ---
|
||||
# 1. Priorität: Manuelle Definition im Frontmatter
|
||||
# --- edge_defaults ---
|
||||
edge_defaults = fm.get("edge_defaults")
|
||||
|
||||
# 2. Priorität: Dynamische Abfrage der 'Typical Edges' aus dem Graph-Schema
|
||||
if edge_defaults is None:
|
||||
topology = edge_registry.get_topology_info(note_type, "any")
|
||||
edge_defaults = topology.get("typical", [])
|
||||
|
||||
# 3. Fallback: Leere Liste, falls kein Schema-Eintrag existiert
|
||||
edge_defaults = cfg_type.get("edge_defaults", cfg_def.get("edge_defaults", []))
|
||||
edge_defaults = _ensure_list(edge_defaults)
|
||||
|
||||
# --- Basis-Metadaten ---
|
||||
|
|
@ -151,24 +138,21 @@ def make_note_payload(note: Any, *args, **kwargs) -> Dict[str, Any]:
|
|||
}
|
||||
|
||||
# --- MULTI-HASH ---
|
||||
# Generiert Hashes für Change Detection (WP-15b)
|
||||
# Generiert Hashes für Change Detection
|
||||
for mode in ["body", "full"]:
|
||||
content = _get_hash_source_content(n, mode)
|
||||
payload["hashes"][f"{mode}:{hash_source}:{hash_normalize}"] = _compute_hash(content)
|
||||
|
||||
# Metadaten Anreicherung (Tags, Aliases, Zeitstempel)
|
||||
# Metadaten Anreicherung
|
||||
tags = fm.get("tags") or fm.get("keywords") or n.get("tags")
|
||||
if tags: payload["tags"] = _ensure_list(tags)
|
||||
|
||||
aliases = fm.get("aliases")
|
||||
if aliases: payload["aliases"] = _ensure_list(aliases)
|
||||
if fm.get("aliases"): payload["aliases"] = _ensure_list(fm.get("aliases"))
|
||||
|
||||
for k in ("created", "modified", "date"):
|
||||
v = fm.get(k) or n.get(k)
|
||||
if v: payload[k] = str(v)
|
||||
|
||||
if n.get("body"):
|
||||
payload["fulltext"] = str(n["body"])
|
||||
if n.get("body"): payload["fulltext"] = str(n["body"])
|
||||
|
||||
# Final JSON Validation Audit
|
||||
json.loads(json.dumps(payload, ensure_ascii=False))
|
||||
|
|
|
|||
|
|
@ -1,22 +1,16 @@
|
|||
"""
|
||||
FILE: app/core/ingestion/ingestion_processor.py
|
||||
DESCRIPTION: Der zentrale IngestionService (Orchestrator).
|
||||
WP-25a: Integration der Mixture of Experts (MoE) Architektur.
|
||||
WP-14: Modularisierung der Datenbank-Ebene (app.core.database).
|
||||
WP-15b: Two-Pass Workflow mit globalem Kontext-Cache.
|
||||
WP-20/22: Cloud-Resilienz und Content-Lifecycle integriert.
|
||||
AUDIT v4.2.4:
|
||||
- GOLD-STANDARD v4.2.4: Hash-basierte Change-Detection (MINDNET_CHANGE_DETECTION_MODE).
|
||||
- Wiederherstellung des iterativen Abgleichs basierend auf Inhalts-Hashes.
|
||||
- Phase 2 verwendet exakt dieselbe ID-Generierung wie Phase 1 (inkl. target_section).
|
||||
- Authority-Check in Phase 2 prüft mit konsistenter ID-Generierung.
|
||||
- Eliminiert Duplikate durch inkonsistente ID-Generierung (Steinzeitaxt-Problem).
|
||||
VERSION: 4.2.4 (WP-24c: Hash-Integrität)
|
||||
AUDIT v2.13.10: Umstellung auf app.core.database Infrastruktur.
|
||||
VERSION: 2.13.10
|
||||
STATUS: Active
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
import os
|
||||
import re
|
||||
from typing import Dict, List, Optional, Tuple, Any
|
||||
|
||||
# Core Module Imports
|
||||
|
|
@ -25,13 +19,10 @@ from app.core.parser import (
|
|||
validate_required_frontmatter, NoteContext
|
||||
)
|
||||
from app.core.chunking import assemble_chunks
|
||||
# WP-24c: Import der zentralen Identitäts-Logik
|
||||
from app.core.graph.graph_utils import _mk_edge_id
|
||||
|
||||
# Datenbank-Ebene (Modularisierte database-Infrastruktur)
|
||||
# MODULARISIERUNG: Neue Import-Pfade für die Datenbank-Ebene
|
||||
from app.core.database.qdrant import QdrantConfig, get_client, ensure_collections, ensure_payload_indexes
|
||||
from app.core.database.qdrant_points import points_for_chunks, points_for_note, points_for_edges, upsert_batch
|
||||
from qdrant_client.http import models as rest
|
||||
|
||||
# Services
|
||||
from app.services.embeddings_client import EmbeddingsClient
|
||||
|
|
@ -40,14 +31,14 @@ from app.services.llm_service import LLMService
|
|||
|
||||
# Package-Interne Imports (Refactoring WP-14)
|
||||
from .ingestion_utils import load_type_registry, resolve_note_type, get_chunk_config_by_profile
|
||||
from .ingestion_db import fetch_note_payload, artifacts_missing, purge_artifacts, is_explicit_edge_present
|
||||
from .ingestion_db import fetch_note_payload, artifacts_missing, purge_artifacts
|
||||
from .ingestion_validation import validate_edge_candidate
|
||||
from .ingestion_note_payload import make_note_payload
|
||||
from .ingestion_chunk_payload import make_chunk_payloads
|
||||
|
||||
# Fallback für Edges (Struktur-Verknüpfung)
|
||||
try:
|
||||
from app.core.graph.graph_derive_edges import build_edges_for_note
|
||||
from app.core.derive_edges import build_edges_for_note
|
||||
except ImportError:
|
||||
def build_edges_for_note(*args, **kwargs): return []
|
||||
|
||||
|
|
@ -59,591 +50,168 @@ class IngestionService:
|
|||
from app.config import get_settings
|
||||
self.settings = get_settings()
|
||||
|
||||
# --- LOGGING CLEANUP ---
|
||||
# Unterdrückt Bibliotheks-Lärm, erhält aber inhaltliche Service-Logs
|
||||
for lib in ["httpx", "httpcore", "qdrant_client", "urllib3", "openai"]:
|
||||
logging.getLogger(lib).setLevel(logging.WARNING)
|
||||
|
||||
self.prefix = collection_prefix or self.settings.COLLECTION_PREFIX
|
||||
self.cfg = QdrantConfig.from_env()
|
||||
# Synchronisierung der Konfiguration mit dem Instanz-Präfix
|
||||
self.cfg.prefix = self.prefix
|
||||
self.client = get_client(self.cfg)
|
||||
|
||||
self.dim = self.settings.VECTOR_SIZE
|
||||
self.registry = load_type_registry()
|
||||
self.embedder = EmbeddingsClient()
|
||||
self.llm = LLMService()
|
||||
|
||||
# WP-25a: Auflösung der Dimension über das Embedding-Profil (MoE)
|
||||
embed_cfg = self.llm.profiles.get("embedding_expert", {})
|
||||
self.dim = embed_cfg.get("dimensions") or self.settings.VECTOR_SIZE
|
||||
|
||||
self.active_hash_mode = self.settings.CHANGE_DETECTION_MODE
|
||||
|
||||
# WP-15b: Kontext-Gedächtnis für ID-Auflösung (Globaler Cache)
|
||||
self.batch_cache: Dict[str, NoteContext] = {}
|
||||
|
||||
# WP-24c: Puffer für Phase 2 (Symmetrie-Injektion am Ende des gesamten Imports)
|
||||
self.symmetry_buffer: List[Dict[str, Any]] = []
|
||||
self.batch_cache: Dict[str, NoteContext] = {} # WP-15b LocalBatchCache
|
||||
|
||||
try:
|
||||
# Aufruf der modularisierten Schema-Logik
|
||||
ensure_collections(self.client, self.prefix, self.dim)
|
||||
ensure_payload_indexes(self.client, self.prefix)
|
||||
except Exception as e:
|
||||
logger.warning(f"DB initialization warning: {e}")
|
||||
|
||||
def _log_id_collision(
|
||||
self,
|
||||
note_id: str,
|
||||
existing_path: str,
|
||||
conflicting_path: str,
|
||||
action: str = "ERROR"
|
||||
) -> None:
|
||||
async def run_batch(self, file_paths: List[str], vault_root: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
WP-24c v4.5.10: Loggt ID-Kollisionen in eine dedizierte Log-Datei.
|
||||
|
||||
Schreibt alle ID-Kollisionen in logs/id_collisions.log für manuelle Analyse.
|
||||
Format: JSONL (eine Kollision pro Zeile) mit allen relevanten Metadaten.
|
||||
|
||||
Args:
|
||||
note_id: Die doppelte note_id
|
||||
existing_path: Pfad der bereits vorhandenen Datei
|
||||
conflicting_path: Pfad der kollidierenden Datei
|
||||
action: Gewählte Aktion (z.B. "ERROR", "SKIPPED")
|
||||
WP-15b: Implementiert den Two-Pass Ingestion Workflow.
|
||||
Pass 1: Pre-Scan füllt den Context-Cache (3-Wege-Indexierung).
|
||||
Pass 2: Verarbeitung nutzt den Cache für die semantische Prüfung.
|
||||
"""
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# Erstelle Log-Verzeichnis falls nicht vorhanden
|
||||
log_dir = "logs"
|
||||
if not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir)
|
||||
|
||||
log_file = os.path.join(log_dir, "id_collisions.log")
|
||||
|
||||
# Erstelle Log-Eintrag mit allen relevanten Informationen
|
||||
log_entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"note_id": note_id,
|
||||
"existing_file": {
|
||||
"path": existing_path,
|
||||
"filename": os.path.basename(existing_path) if existing_path else None
|
||||
},
|
||||
"conflicting_file": {
|
||||
"path": conflicting_path,
|
||||
"filename": os.path.basename(conflicting_path) if conflicting_path else None
|
||||
},
|
||||
"action": action,
|
||||
"collection_prefix": self.prefix
|
||||
}
|
||||
|
||||
# Schreibe als JSONL (eine Zeile pro Eintrag)
|
||||
try:
|
||||
with open(log_file, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Konnte ID-Kollision nicht in Log-Datei schreiben: {e}")
|
||||
|
||||
def _persist_rejected_edges(self, note_id: str, rejected_edges: List[Dict[str, Any]]) -> None:
|
||||
"""
|
||||
WP-24c v4.5.9: Persistiert abgelehnte Kanten für Audit-Zwecke.
|
||||
|
||||
Schreibt rejected_edges in eine JSONL-Datei im _system Ordner oder logs/rejected_edges.log.
|
||||
Dies ermöglicht die Analyse der Ablehnungsgründe und Verbesserung der Validierungs-Logik.
|
||||
|
||||
Args:
|
||||
note_id: ID der Note, zu der die abgelehnten Kanten gehören
|
||||
rejected_edges: Liste von abgelehnten Edge-Dicts
|
||||
"""
|
||||
if not rejected_edges:
|
||||
return
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
# WP-24c v4.5.9: Erstelle Log-Verzeichnis falls nicht vorhanden
|
||||
log_dir = "logs"
|
||||
if not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir)
|
||||
|
||||
log_file = os.path.join(log_dir, "rejected_edges.log")
|
||||
|
||||
# WP-24c v4.5.9: Schreibe als JSONL (eine Kante pro Zeile)
|
||||
try:
|
||||
with open(log_file, "a", encoding="utf-8") as f:
|
||||
for edge in rejected_edges:
|
||||
log_entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"note_id": note_id,
|
||||
"edge": {
|
||||
"kind": edge.get("kind", "unknown"),
|
||||
"source_id": edge.get("source_id", "unknown"),
|
||||
"target_id": edge.get("target_id") or edge.get("to", "unknown"),
|
||||
"scope": edge.get("scope", "unknown"),
|
||||
"provenance": edge.get("provenance", "unknown"),
|
||||
"rule_id": edge.get("rule_id", "unknown"),
|
||||
"confidence": edge.get("confidence", 0.0),
|
||||
"target_section": edge.get("target_section")
|
||||
}
|
||||
}
|
||||
f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
|
||||
|
||||
logger.debug(f"📝 [AUDIT] {len(rejected_edges)} abgelehnte Kanten für '{note_id}' in {log_file} gespeichert")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ [AUDIT] Fehler beim Speichern der rejected_edges: {e}")
|
||||
|
||||
def _is_valid_id(self, text: Optional[str]) -> bool:
|
||||
"""WP-24c: Prüft IDs auf fachliche Validität (Ghost-ID Schutz)."""
|
||||
if not text or not isinstance(text, str) or len(text.strip()) < 2:
|
||||
return False
|
||||
blacklisted = {"none", "unknown", "insight", "source", "task", "project", "person", "concept"}
|
||||
if text.lower().strip() in blacklisted:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def run_batch(self, file_paths: List[str], vault_root: str) -> Dict[str, Any]:
|
||||
"""
|
||||
WP-15b: Phase 1 des Two-Pass Workflows.
|
||||
Verarbeitet Batches und schreibt NUR Nutzer-Autorität (explizite Kanten).
|
||||
"""
|
||||
self.batch_cache.clear()
|
||||
logger.info(f"--- 🔍 START BATCH PHASE 1 ({len(file_paths)} Dateien) ---")
|
||||
|
||||
# 1. Schritt: Pre-Scan (Context-Cache füllen)
|
||||
logger.info(f"🔍 [Pass 1] Pre-Scanning {len(file_paths)} files for Context Cache...")
|
||||
for path in file_paths:
|
||||
try:
|
||||
# Übergabe der Registry für dynamische Scan-Tiefe
|
||||
ctx = pre_scan_markdown(path, registry=self.registry)
|
||||
if ctx:
|
||||
# Mehrfache Indizierung für robusten Look-up (ID, Titel, Dateiname)
|
||||
self.batch_cache[ctx.note_id] = ctx
|
||||
self.batch_cache[ctx.title] = ctx
|
||||
self.batch_cache[os.path.splitext(os.path.basename(path))[0]] = ctx
|
||||
fname = os.path.splitext(os.path.basename(path))[0]
|
||||
self.batch_cache[fname] = ctx
|
||||
except Exception as e:
|
||||
logger.warning(f" ⚠️ Pre-scan fehlgeschlagen für {path}: {e}")
|
||||
logger.warning(f"⚠️ Pre-scan failed for {path}: {e}")
|
||||
|
||||
# 2. Schritt: Batch Processing (Authority Only)
|
||||
processed_count = 0
|
||||
success_count = 0
|
||||
for p in file_paths:
|
||||
processed_count += 1
|
||||
res = await self.process_file(p, vault_root, apply=True, purge_before=True)
|
||||
if res.get("status") == "success":
|
||||
success_count += 1
|
||||
|
||||
logger.info(f"--- ✅ Batch Phase 1 abgeschlossen ({success_count}/{processed_count}) ---")
|
||||
return {
|
||||
"status": "success",
|
||||
"processed": processed_count,
|
||||
"success": success_count,
|
||||
"buffered_symmetries": len(self.symmetry_buffer)
|
||||
}
|
||||
|
||||
async def commit_vault_symmetries(self) -> Dict[str, Any]:
|
||||
"""
|
||||
WP-24c: Führt PHASE 2 (Globale Symmetrie-Injektion) aus.
|
||||
Wird am Ende des gesamten Imports aufgerufen.
|
||||
"""
|
||||
if not self.symmetry_buffer:
|
||||
return {"status": "skipped", "reason": "buffer_empty"}
|
||||
|
||||
logger.info(f"🔄 PHASE 2: Validiere {len(self.symmetry_buffer)} Symmetrien gegen Live-DB...")
|
||||
final_virtuals = []
|
||||
for v_edge in self.symmetry_buffer:
|
||||
# WP-24c v4.1.0: Korrekte Extraktion der Identitäts-Parameter
|
||||
src = v_edge.get("source_id") or v_edge.get("note_id") # source_id hat Priorität
|
||||
tgt = v_edge.get("target_id")
|
||||
kind = v_edge.get("kind")
|
||||
scope = v_edge.get("scope", "note")
|
||||
target_section = v_edge.get("target_section") # WP-24c v4.1.0: target_section berücksichtigen
|
||||
|
||||
if not all([src, tgt, kind]):
|
||||
continue
|
||||
|
||||
# WP-24c v4.1.0: Nutzung der zentralisierten ID-Logik aus graph_utils
|
||||
# GOLD-STANDARD v4.1.0: ID-Generierung muss absolut synchron zu Phase 1 sein
|
||||
# - Wenn target_section vorhanden, muss es in die ID einfließen
|
||||
# - Dies stellt sicher, dass der Authority-Check korrekt funktioniert
|
||||
try:
|
||||
v_id = _mk_edge_id(kind, src, tgt, scope, target_section=target_section)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# AUTHORITY-CHECK: Nur schreiben, wenn keine manuelle Kante existiert
|
||||
# Prüft mit exakt derselben ID, die in Phase 1 verwendet wurde (inkl. target_section)
|
||||
if not is_explicit_edge_present(self.client, self.prefix, v_id):
|
||||
final_virtuals.append(v_edge)
|
||||
section_info = f" (section: {target_section})" if target_section else ""
|
||||
logger.info(f" 🔄 [SYMMETRY] Add inverse: {src} --({kind})--> {tgt}{section_info}")
|
||||
else:
|
||||
logger.info(f" 🛡️ [PROTECTED] Manuelle Kante gefunden. Symmetrie für {kind} unterdrückt.")
|
||||
|
||||
if final_virtuals:
|
||||
col, pts = points_for_edges(self.prefix, final_virtuals)
|
||||
upsert_batch(self.client, col, pts, wait=True)
|
||||
|
||||
count = len(final_virtuals)
|
||||
self.symmetry_buffer.clear()
|
||||
return {"status": "success", "added": count}
|
||||
logger.info(f"🚀 [Pass 2] Semantic Processing of {len(file_paths)} files...")
|
||||
return [await self.process_file(p, vault_root, apply=True, purge_before=True) for p in file_paths]
|
||||
|
||||
async def process_file(self, file_path: str, vault_root: str, **kwargs) -> Dict[str, Any]:
|
||||
"""
|
||||
Transformiert eine Markdown-Datei (Phase 1).
|
||||
Schreibt Notes/Chunks/Explicit Edges sofort.
|
||||
"""
|
||||
"""Transformiert eine Markdown-Datei in den Graphen."""
|
||||
apply = kwargs.get("apply", False)
|
||||
force_replace = kwargs.get("force_replace", False)
|
||||
purge_before = kwargs.get("purge_before", False)
|
||||
note_scope_refs = kwargs.get("note_scope_refs", False)
|
||||
hash_source = kwargs.get("hash_source", "parsed")
|
||||
hash_normalize = kwargs.get("hash_normalize", "canonical")
|
||||
|
||||
result = {"path": file_path, "status": "skipped", "changed": False, "error": None}
|
||||
|
||||
# 1. Parse & Lifecycle Gate
|
||||
try:
|
||||
# Ordner-Filter (.trash / .obsidian)
|
||||
if ".trash" in file_path or any(part.startswith('.') for part in file_path.split(os.sep)):
|
||||
return {**result, "status": "skipped", "reason": "ignored_folder"}
|
||||
|
||||
# WP-24c v4.5.9: Path-Normalization für konsistente Hash-Prüfung
|
||||
# Normalisiere file_path zu absolutem Pfad für konsistente Verarbeitung
|
||||
normalized_file_path = os.path.abspath(file_path) if not os.path.isabs(file_path) else file_path
|
||||
|
||||
parsed = read_markdown(normalized_file_path)
|
||||
parsed = read_markdown(file_path)
|
||||
if not parsed: return {**result, "error": "Empty file"}
|
||||
fm = normalize_frontmatter(parsed.frontmatter)
|
||||
validate_required_frontmatter(fm)
|
||||
except Exception as e:
|
||||
return {**result, "error": f"Validation failed: {str(e)}"}
|
||||
|
||||
note_pl = make_note_payload(parsed, vault_root=vault_root, file_path=normalized_file_path, types_cfg=self.registry)
|
||||
note_id = note_pl.get("note_id")
|
||||
# Dynamischer Lifecycle-Filter aus der Registry (WP-14)
|
||||
ingest_cfg = self.registry.get("ingestion_settings", {})
|
||||
ignore_list = ingest_cfg.get("ignore_statuses", ["system", "template", "archive", "hidden"])
|
||||
|
||||
if not note_id:
|
||||
return {**result, "status": "error", "error": "missing_id"}
|
||||
current_status = fm.get("status", "draft").lower().strip()
|
||||
if current_status in ignore_list:
|
||||
return {**result, "status": "skipped", "reason": "lifecycle_filter"}
|
||||
|
||||
logger.info(f"📄 Bearbeite: '{note_id}' | Pfad: {normalized_file_path} | Title: {note_pl.get('title', 'N/A')}")
|
||||
# 2. Payload & Change Detection (Multi-Hash)
|
||||
note_type = resolve_note_type(self.registry, fm.get("type"))
|
||||
note_pl = make_note_payload(
|
||||
parsed, vault_root=vault_root, file_path=file_path,
|
||||
hash_source=hash_source, hash_normalize=hash_normalize,
|
||||
types_cfg=self.registry
|
||||
)
|
||||
note_id = note_pl["note_id"]
|
||||
|
||||
# WP-24c v4.5.9: Strikte Change Detection (Hash-basierte Inhaltsprüfung)
|
||||
# Prüft Hash VOR der Verarbeitung, um redundante Ingestion zu vermeiden
|
||||
old_payload = None if force_replace else fetch_note_payload(self.client, self.prefix, note_id)
|
||||
check_key = f"{self.active_hash_mode}:{hash_source}:{hash_normalize}"
|
||||
old_hash = (old_payload or {}).get("hashes", {}).get(check_key)
|
||||
new_hash = note_pl.get("hashes", {}).get(check_key)
|
||||
|
||||
# WP-24c v4.5.10: Prüfe auf ID-Kollisionen (zwei Dateien mit derselben note_id)
|
||||
if old_payload and not force_replace:
|
||||
old_path = old_payload.get("path", "")
|
||||
if old_path and old_path != normalized_file_path:
|
||||
# ID-Kollision erkannt: Zwei verschiedene Dateien haben dieselbe note_id
|
||||
# Logge die Kollision in dedizierte Log-Datei
|
||||
self._log_id_collision(
|
||||
note_id=note_id,
|
||||
existing_path=old_path,
|
||||
conflicting_path=normalized_file_path,
|
||||
action="ERROR"
|
||||
)
|
||||
logger.error(
|
||||
f"❌ [ID-KOLLISION] Kritischer Fehler: Die note_id '{note_id}' wird bereits von einer anderen Datei verwendet!\n"
|
||||
f" Bereits vorhanden: '{old_path}'\n"
|
||||
f" Konflikt mit: '{normalized_file_path}'\n"
|
||||
f" Lösung: Bitte ändern Sie die 'id' im Frontmatter einer der beiden Dateien, um eine eindeutige ID zu gewährleisten.\n"
|
||||
f" Details wurden in logs/id_collisions.log gespeichert."
|
||||
)
|
||||
return {**result, "status": "error", "error": "id_collision", "note_id": note_id, "existing_path": old_path, "conflicting_path": normalized_file_path}
|
||||
|
||||
logger.debug(f"🔍 [CHANGE-DETECTION] Start für '{note_id}': force_replace={force_replace}, old_payload={old_payload is not None}")
|
||||
|
||||
content_changed = True
|
||||
hash_match = False
|
||||
if old_payload and not force_replace:
|
||||
# Nutzt die über MINDNET_CHANGE_DETECTION_MODE gesteuerte Genauigkeit
|
||||
# Mapping: 'full' -> 'full:parsed:canonical', 'body' -> 'body:parsed:canonical'
|
||||
h_key = f"{self.active_hash_mode or 'full'}:parsed:canonical"
|
||||
new_h = note_pl.get("hashes", {}).get(h_key)
|
||||
old_h = old_payload.get("hashes", {}).get(h_key)
|
||||
|
||||
# WP-24c v4.5.9-DEBUG: Detaillierte Hash-Diagnose (INFO-Level)
|
||||
logger.info(f"🔍 [CHANGE-DETECTION] Hash-Vergleich für '{note_id}':")
|
||||
logger.debug(f" -> Hash-Key: '{h_key}'")
|
||||
logger.debug(f" -> Active Hash-Mode: '{self.active_hash_mode or 'full'}'")
|
||||
logger.debug(f" -> New Hash vorhanden: {bool(new_h)}")
|
||||
logger.debug(f" -> Old Hash vorhanden: {bool(old_h)}")
|
||||
if new_h:
|
||||
logger.debug(f" -> New Hash (erste 32 Zeichen): {new_h[:32]}...")
|
||||
if old_h:
|
||||
logger.debug(f" -> Old Hash (erste 32 Zeichen): {old_h[:32]}...")
|
||||
logger.debug(f" -> Verfügbare Hash-Keys in new: {list(note_pl.get('hashes', {}).keys())}")
|
||||
logger.debug(f" -> Verfügbare Hash-Keys in old: {list(old_payload.get('hashes', {}).keys())}")
|
||||
|
||||
if new_h and old_h:
|
||||
hash_match = (new_h == old_h)
|
||||
if hash_match:
|
||||
content_changed = False
|
||||
logger.info(f"🔍 [CHANGE-DETECTION] ✅ Hash identisch für '{note_id}': {h_key} = {new_h[:16]}...")
|
||||
else:
|
||||
logger.warning(f"🔍 [CHANGE-DETECTION] ❌ Hash geändert für '{note_id}': alt={old_h[:16]}..., neu={new_h[:16]}...")
|
||||
# Finde erste unterschiedliche Position
|
||||
diff_pos = next((i for i, (a, b) in enumerate(zip(new_h, old_h)) if a != b), None)
|
||||
if diff_pos is not None:
|
||||
logger.debug(f" -> Hash-Unterschied: Erste unterschiedliche Position: {diff_pos}")
|
||||
else:
|
||||
logger.debug(f" -> Hash-Unterschied: Längen unterschiedlich (new={len(new_h)}, old={len(old_h)})")
|
||||
|
||||
# WP-24c v4.5.10: Logge Hash-Input für Diagnose (DEBUG-Level)
|
||||
# WICHTIG: _get_hash_source_content benötigt ein Dictionary, nicht das ParsedNote-Objekt!
|
||||
from app.core.ingestion.ingestion_note_payload import _get_hash_source_content, _as_dict
|
||||
hash_mode = self.active_hash_mode or 'full'
|
||||
# Konvertiere parsed zu Dictionary für _get_hash_source_content
|
||||
parsed_dict = _as_dict(parsed)
|
||||
hash_input = _get_hash_source_content(parsed_dict, hash_mode)
|
||||
logger.debug(f" -> Hash-Input (erste 200 Zeichen): {hash_input[:200]}...")
|
||||
logger.debug(f" -> Hash-Input Länge: {len(hash_input)}")
|
||||
|
||||
# WP-24c v4.5.10: Vergleiche auch Body-Länge und Frontmatter (DEBUG-Level)
|
||||
# Verwende parsed.body statt note_pl.get("body")
|
||||
new_body = str(getattr(parsed, "body", "") or "").strip()
|
||||
old_body = str(old_payload.get("body", "")).strip() if old_payload else ""
|
||||
logger.debug(f" -> Body-Länge: new={len(new_body)}, old={len(old_body)}")
|
||||
if len(new_body) != len(old_body):
|
||||
logger.debug(f" -> ⚠️ Body-Länge unterschiedlich! Mögliche Ursache: Parsing-Unterschiede")
|
||||
|
||||
# Verwende parsed.frontmatter statt note_pl.get("frontmatter")
|
||||
new_fm = getattr(parsed, "frontmatter", {}) or {}
|
||||
old_fm = old_payload.get("frontmatter", {}) if old_payload else {}
|
||||
logger.debug(f" -> Frontmatter-Keys: new={sorted(new_fm.keys())}, old={sorted(old_fm.keys())}")
|
||||
# Prüfe relevante Frontmatter-Felder
|
||||
relevant_keys = ["title", "type", "status", "tags", "chunking_profile", "chunk_profile", "retriever_weight", "split_level", "strict_heading_split"]
|
||||
for key in relevant_keys:
|
||||
new_val = new_fm.get(key) if isinstance(new_fm, dict) else getattr(new_fm, key, None)
|
||||
old_val = old_fm.get(key) if isinstance(old_fm, dict) else None
|
||||
if new_val != old_val:
|
||||
logger.debug(f" -> ⚠️ Frontmatter '{key}' unterschiedlich: new={new_val}, old={old_val}")
|
||||
else:
|
||||
# WP-24c v4.5.10: Wenn Hash fehlt, als geändert behandeln (Sicherheit)
|
||||
logger.debug(f"⚠️ [CHANGE-DETECTION] Hash fehlt für '{note_id}': new_h={bool(new_h)}, old_h={bool(old_h)}")
|
||||
logger.debug(f" -> Grund: Hash wird als 'geändert' behandelt, da Hash-Werte fehlen")
|
||||
else:
|
||||
if force_replace:
|
||||
logger.debug(f"🔍 [CHANGE-DETECTION] '{note_id}': force_replace=True -> überspringe Hash-Check")
|
||||
elif not old_payload:
|
||||
logger.debug(f"🔍 [CHANGE-DETECTION] '{note_id}': ⚠️ Keine alte Payload gefunden -> erste Verarbeitung oder gelöscht")
|
||||
|
||||
# WP-24c v4.5.9: Strikte Logik - überspringe komplett wenn Hash identisch
|
||||
# WICHTIG: Artifact-Check NACH Hash-Check, da purge_before die Artefakte löschen kann
|
||||
# Wenn Hash identisch ist, sind die Artefakte entweder vorhanden oder werden gerade neu geschrieben
|
||||
if not force_replace and hash_match and old_payload:
|
||||
# WP-24c v4.5.9: Hash identisch -> überspringe komplett (auch wenn Artefakte nach PURGE fehlen)
|
||||
# Der Hash ist die autoritative Quelle für "Inhalt unverändert"
|
||||
# Artefakte werden beim nächsten normalen Import wieder erstellt, wenn nötig
|
||||
logger.info(f"⏭️ [SKIP] '{note_id}' unverändert (Hash identisch - überspringe komplett, auch wenn Artefakte fehlen)")
|
||||
return {**result, "status": "unchanged", "note_id": note_id, "reason": "hash_identical"}
|
||||
elif not force_replace and old_payload and not hash_match:
|
||||
# WP-24c v4.5.10: Hash geändert - erlaube Verarbeitung (DEBUG-Level)
|
||||
logger.debug(f"🔍 [CHANGE-DETECTION] '{note_id}': Hash geändert -> erlaube Verarbeitung")
|
||||
|
||||
# WP-24c v4.5.10: Hash geändert oder keine alte Payload - prüfe Artefakte für normale Verarbeitung
|
||||
c_miss, e_miss = artifacts_missing(self.client, self.prefix, note_id)
|
||||
logger.debug(f"🔍 [CHANGE-DETECTION] '{note_id}': Artifact-Check: c_miss={c_miss}, e_miss={e_miss}")
|
||||
if not (force_replace or not old_payload or old_hash != new_hash or c_miss or e_miss):
|
||||
return {**result, "status": "unchanged", "note_id": note_id}
|
||||
|
||||
if not apply:
|
||||
return {**result, "status": "dry-run", "changed": True, "note_id": note_id}
|
||||
|
||||
# Chunks & MoE
|
||||
profile = note_pl.get("chunk_profile", "sliding_standard")
|
||||
note_type = resolve_note_type(self.registry, fm.get("type"))
|
||||
# 3. Deep Processing (Chunking, Validation, Embedding)
|
||||
try:
|
||||
body_text = getattr(parsed, "body", "") or ""
|
||||
edge_registry.ensure_latest()
|
||||
profile = fm.get("chunk_profile") or fm.get("chunking_profile") or "sliding_standard"
|
||||
chunk_cfg = get_chunk_config_by_profile(self.registry, profile, note_type)
|
||||
enable_smart = chunk_cfg.get("enable_smart_edge_allocation", False)
|
||||
chunks = await assemble_chunks(note_id, getattr(parsed, "body", ""), note_type, config=chunk_cfg)
|
||||
|
||||
# WP-24c v4.5.8: Validierung in Chunk-Schleife entfernt
|
||||
# Alle candidate: Kanten werden jetzt in Phase 3 (nach build_edges_for_note) validiert
|
||||
# Dies stellt sicher, dass auch Note-Scope Kanten aus LLM-Validierungs-Zonen geprüft werden
|
||||
# Der candidate_pool wird unverändert weitergegeben, damit build_edges_for_note alle Kanten erkennt
|
||||
# WP-24c v4.5.8: Nur ID-Validierung bleibt (Ghost-ID Schutz), keine LLM-Validierung mehr hier
|
||||
# WP-15b: Chunker-Aufruf bereitet Candidate-Pool vor
|
||||
chunks = await assemble_chunks(note_id, body_text, note_type, config=chunk_cfg)
|
||||
for ch in chunks:
|
||||
new_pool = []
|
||||
filtered = []
|
||||
for cand in getattr(ch, "candidate_pool", []):
|
||||
# WP-24c v4.5.8: Nur ID-Validierung (Ghost-ID Schutz)
|
||||
t_id = cand.get('target_id') or cand.get('to') or cand.get('note_id')
|
||||
if not self._is_valid_id(t_id):
|
||||
continue
|
||||
# WP-24c v4.5.8: Alle Kanten gehen durch - LLM-Validierung erfolgt in Phase 3
|
||||
new_pool.append(cand)
|
||||
ch.candidate_pool = new_pool
|
||||
|
||||
# chunk_pls = make_chunk_payloads(fm, note_pl["path"], chunks, file_path=file_path, types_cfg=self.registry)
|
||||
# v4.2.8 Fix C: Explizite Übergabe des Profil-Namens für den Chunk-Payload
|
||||
chunk_pls = make_chunk_payloads(fm, note_pl["path"], chunks, file_path=file_path, types_cfg=self.registry, chunk_profile=profile)
|
||||
# WP-15b: Nur global_pool Kandidaten erfordern binäre Validierung
|
||||
if cand.get("provenance") == "global_pool" and enable_smart:
|
||||
if await validate_edge_candidate(ch.text, cand, self.batch_cache, self.llm, self.settings.MINDNET_LLM_PROVIDER):
|
||||
filtered.append(cand)
|
||||
else:
|
||||
filtered.append(cand)
|
||||
ch.candidate_pool = filtered
|
||||
|
||||
# Payload-Erstellung via interne Module
|
||||
chunk_pls = make_chunk_payloads(
|
||||
fm, note_pl["path"], chunks, file_path=file_path,
|
||||
types_cfg=self.registry
|
||||
)
|
||||
vecs = await self.embedder.embed_documents([c.get("window") or "" for c in chunk_pls]) if chunk_pls else []
|
||||
|
||||
# WP-24c v4.2.0: Kanten-Extraktion mit Note-Scope Zonen Support
|
||||
# Übergabe des Original-Markdown-Texts für Note-Scope Zonen-Extraktion
|
||||
markdown_body = getattr(parsed, "body", "")
|
||||
raw_edges = build_edges_for_note(
|
||||
note_id,
|
||||
chunk_pls,
|
||||
# Kanten-Aggregation
|
||||
edges = build_edges_for_note(
|
||||
note_id, chunk_pls,
|
||||
note_level_references=note_pl.get("references", []),
|
||||
markdown_body=markdown_body
|
||||
include_note_scope_refs=note_scope_refs
|
||||
)
|
||||
for e in edges:
|
||||
e["kind"] = edge_registry.resolve(
|
||||
e.get("kind", "related_to"),
|
||||
provenance=e.get("provenance", "explicit"),
|
||||
context={"file": file_path, "note_id": note_id, "line": e.get("line", "system")}
|
||||
)
|
||||
|
||||
# WP-24c v4.5.8: Phase 3 - Finaler Validierungs-Gate für candidate: Kanten
|
||||
# Prüfe alle Kanten mit rule_id ODER provenance beginnend mit "candidate:"
|
||||
# Dies schließt alle Kandidaten ein, unabhängig von ihrer Herkunft (global_pool, explicit:callout, etc.)
|
||||
# 4. DB Upsert via modularisierter Points-Logik
|
||||
if purge_before and old_payload:
|
||||
purge_artifacts(self.client, self.prefix, note_id)
|
||||
|
||||
# WP-24c v4.5.8: Kontext-Optimierung für Note-Scope Kanten
|
||||
# Aggregiere den gesamten Note-Text für bessere Validierungs-Entscheidungen
|
||||
note_text = markdown_body or " ".join([c.get("text", "") or c.get("window", "") for c in chunk_pls])
|
||||
# Erstelle eine Note-Summary aus den wichtigsten Chunks (für bessere Kontext-Qualität)
|
||||
note_summary = " ".join([c.get("window", "") or c.get("text", "") for c in chunk_pls[:5]]) # Top 5 Chunks
|
||||
|
||||
validated_edges = []
|
||||
rejected_edges = []
|
||||
|
||||
for e in raw_edges:
|
||||
rule_id = e.get("rule_id", "")
|
||||
provenance = e.get("provenance", "")
|
||||
|
||||
# WP-24c v4.5.8: Trigger-Kriterium - rule_id ODER provenance beginnt mit "candidate:"
|
||||
is_candidate = (rule_id and rule_id.startswith("candidate:")) or (provenance and provenance.startswith("candidate:"))
|
||||
|
||||
if is_candidate:
|
||||
# Extrahiere target_id für Validierung (aus verschiedenen möglichen Feldern)
|
||||
target_id = e.get("target_id") or e.get("to")
|
||||
if not target_id:
|
||||
# Fallback: Versuche aus Payload zu extrahieren
|
||||
payload = e.get("extra", {}) if isinstance(e.get("extra"), dict) else {}
|
||||
target_id = payload.get("target_id") or payload.get("to")
|
||||
|
||||
if not target_id:
|
||||
logger.warning(f"⚠️ [PHASE 3] Keine target_id gefunden für Kante: {e}")
|
||||
rejected_edges.append(e)
|
||||
continue
|
||||
|
||||
kind = e.get("kind", "related_to")
|
||||
source_id = e.get("source_id", note_id)
|
||||
scope = e.get("scope", "chunk")
|
||||
|
||||
# WP-24c v4.5.8: Kontext-Optimierung für Note-Scope Kanten
|
||||
# Für scope: note verwende Note-Summary oder gesamten Note-Text
|
||||
# Für scope: chunk verwende den spezifischen Chunk-Text (falls verfügbar)
|
||||
if scope == "note":
|
||||
validation_text = note_summary or note_text
|
||||
context_info = "Note-Scope (aggregiert)"
|
||||
else:
|
||||
# Für Chunk-Scope: Versuche Chunk-Text zu finden, sonst Note-Text
|
||||
chunk_id = e.get("chunk_id") or source_id
|
||||
chunk_text = None
|
||||
for ch in chunk_pls:
|
||||
if ch.get("chunk_id") == chunk_id or ch.get("id") == chunk_id:
|
||||
chunk_text = ch.get("text") or ch.get("window", "")
|
||||
break
|
||||
validation_text = chunk_text or note_text
|
||||
context_info = f"Chunk-Scope ({chunk_id})"
|
||||
|
||||
# Erstelle Edge-Dict für Validierung (kompatibel mit validate_edge_candidate)
|
||||
edge_for_validation = {
|
||||
"kind": kind,
|
||||
"to": target_id, # validate_edge_candidate erwartet "to"
|
||||
"target_id": target_id,
|
||||
"provenance": provenance if not provenance.startswith("candidate:") else provenance.replace("candidate:", "").strip(),
|
||||
"confidence": e.get("confidence", 0.9)
|
||||
}
|
||||
|
||||
logger.info(f"🚀 [PHASE 3] Validierung: {source_id} -> {target_id} ({kind}) | Scope: {scope} | Kontext: {context_info}")
|
||||
|
||||
# WP-24c v4.5.8: Validiere gegen optimierten Kontext
|
||||
is_valid = await validate_edge_candidate(
|
||||
chunk_text=validation_text,
|
||||
edge=edge_for_validation,
|
||||
batch_cache=self.batch_cache,
|
||||
llm_service=self.llm,
|
||||
profile_name="ingest_validator"
|
||||
)
|
||||
|
||||
if is_valid:
|
||||
# WP-24c v4.5.8: Entferne candidate: Präfix (Kante wird zum Fakt)
|
||||
new_rule_id = rule_id.replace("candidate:", "").strip() if rule_id else provenance.replace("candidate:", "").strip() if provenance.startswith("candidate:") else provenance
|
||||
if not new_rule_id:
|
||||
new_rule_id = e.get("provenance", "explicit").replace("candidate:", "").strip()
|
||||
|
||||
# Aktualisiere rule_id und provenance im Edge
|
||||
e["rule_id"] = new_rule_id
|
||||
if provenance.startswith("candidate:"):
|
||||
e["provenance"] = provenance.replace("candidate:", "").strip()
|
||||
|
||||
validated_edges.append(e)
|
||||
logger.info(f"✅ [PHASE 3] VERIFIED: {source_id} -> {target_id} ({kind}) | rule_id: {new_rule_id}")
|
||||
else:
|
||||
# WP-24c v4.5.8: Kante ablehnen (nicht zu validated_edges hinzufügen)
|
||||
rejected_edges.append(e)
|
||||
logger.info(f"🚫 [PHASE 3] REJECTED: {source_id} -> {target_id} ({kind})")
|
||||
else:
|
||||
# WP-24c v4.5.8: Keine candidate: Kante -> direkt übernehmen
|
||||
validated_edges.append(e)
|
||||
|
||||
# WP-24c v4.5.8: Phase 3 abgeschlossen - rejected_edges werden NICHT weiterverarbeitet
|
||||
# WP-24c v4.5.9: Persistierung von rejected_edges für Audit-Zwecke
|
||||
if rejected_edges:
|
||||
logger.info(f"🚫 [PHASE 3] {len(rejected_edges)} Kanten abgelehnt und werden nicht in die DB geschrieben")
|
||||
self._persist_rejected_edges(note_id, rejected_edges)
|
||||
|
||||
# WP-24c v4.5.8: Verwende validated_edges statt raw_edges für weitere Verarbeitung
|
||||
# Nur verified Kanten (ohne candidate: Präfix) werden in Phase 2 (Symmetrie) verarbeitet
|
||||
explicit_edges = []
|
||||
for e in validated_edges:
|
||||
t_raw = e.get("target_id")
|
||||
t_ctx = self.batch_cache.get(t_raw)
|
||||
t_id = t_ctx.note_id if t_ctx else t_raw
|
||||
|
||||
if not self._is_valid_id(t_id): continue
|
||||
|
||||
resolved_kind = edge_registry.resolve(e.get("kind", "related_to"), provenance="explicit")
|
||||
# WP-24c v4.1.0: target_section aus dem Edge-Payload extrahieren und beibehalten
|
||||
target_section = e.get("target_section")
|
||||
e.update({
|
||||
"kind": resolved_kind,
|
||||
"relation": resolved_kind, # Konsistenz: kind und relation identisch
|
||||
"target_id": t_id,
|
||||
"source_id": e.get("source_id") or note_id, # Sicherstellen, dass source_id gesetzt ist
|
||||
"origin_note_id": note_id,
|
||||
"virtual": False
|
||||
})
|
||||
explicit_edges.append(e)
|
||||
|
||||
# Symmetrie puffern (WP-24c v4.1.0: Korrekte Symmetrie-Integrität)
|
||||
inv_kind = edge_registry.get_inverse(resolved_kind)
|
||||
if inv_kind and t_id != note_id:
|
||||
# GOLD-STANDARD v4.1.0: Symmetrie-Integrität
|
||||
v_edge = {
|
||||
"note_id": t_id, # Besitzer-Wechsel: Symmetrie gehört zum Link-Ziel
|
||||
"source_id": t_id, # Neue Quelle ist das Link-Ziel
|
||||
"target_id": note_id, # Ziel ist die ursprüngliche Quelle
|
||||
"kind": inv_kind, # Inverser Kanten-Typ
|
||||
"relation": inv_kind, # Konsistenz: kind und relation identisch
|
||||
"scope": "note", # Symmetrien sind immer Note-Level
|
||||
"virtual": True,
|
||||
"origin_note_id": note_id, # Tracking: Woher kommt die Symmetrie
|
||||
}
|
||||
# target_section beibehalten, falls vorhanden (für Section-Links)
|
||||
if target_section:
|
||||
v_edge["target_section"] = target_section
|
||||
self.symmetry_buffer.append(v_edge)
|
||||
|
||||
# DB Upsert
|
||||
if purge_before and old_payload: purge_artifacts(self.client, self.prefix, note_id)
|
||||
|
||||
col_n, pts_n = points_for_note(self.prefix, note_pl, None, self.dim)
|
||||
upsert_batch(self.client, col_n, pts_n, wait=True)
|
||||
n_name, n_pts = points_for_note(self.prefix, note_pl, None, self.dim)
|
||||
upsert_batch(self.client, n_name, n_pts)
|
||||
|
||||
if chunk_pls and vecs:
|
||||
col_c, pts_c = points_for_chunks(self.prefix, chunk_pls, vecs)
|
||||
upsert_batch(self.client, col_c, pts_c, wait=True)
|
||||
c_pts = points_for_chunks(self.prefix, chunk_pls, vecs)[1]
|
||||
upsert_batch(self.client, f"{self.prefix}_chunks", c_pts)
|
||||
|
||||
if explicit_edges:
|
||||
col_e, pts_e = points_for_edges(self.prefix, explicit_edges)
|
||||
upsert_batch(self.client, col_e, pts_e, wait=True)
|
||||
|
||||
logger.info(f" ✨ Phase 1 fertig: {len(explicit_edges)} explizite Kanten für '{note_id}'.")
|
||||
return {"status": "success", "note_id": note_id}
|
||||
if edges:
|
||||
e_pts = points_for_edges(self.prefix, edges)[1]
|
||||
upsert_batch(self.client, f"{self.prefix}_edges", e_pts)
|
||||
|
||||
return {
|
||||
"path": file_path,
|
||||
"status": "success",
|
||||
"changed": True,
|
||||
"note_id": note_id,
|
||||
"chunks_count": len(chunk_pls),
|
||||
"edges_count": len(edges)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Fehler bei {file_path}: {e}", exc_info=True)
|
||||
return {**result, "status": "error", "error": str(e)}
|
||||
logger.error(f"Processing failed: {e}", exc_info=True)
|
||||
return {**result, "error": str(e)}
|
||||
|
||||
async def create_from_text(self, markdown_content: str, filename: str, vault_root: str, folder: str = "00_Inbox") -> Dict[str, Any]:
|
||||
"""Erstellt eine Note aus einem Textstream."""
|
||||
"""Erstellt eine Note aus einem Textstream und triggert die Ingestion."""
|
||||
target_path = os.path.join(vault_root, folder, filename)
|
||||
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
||||
with open(target_path, "w", encoding="utf-8") as f:
|
||||
|
|
|
|||
|
|
@ -1,23 +1,14 @@
|
|||
"""
|
||||
FILE: app/core/ingestion/ingestion_validation.py
|
||||
DESCRIPTION: WP-15b semantische Validierung von Kanten gegen den LocalBatchCache.
|
||||
WP-24c: Erweiterung um automatische Symmetrie-Generierung (Inverse Kanten).
|
||||
WP-25b: Konsequente Lazy-Prompt-Orchestration (prompt_key + variables).
|
||||
VERSION: 3.0.0 (WP-24c: Symmetric Edge Management)
|
||||
STATUS: Active
|
||||
FIX:
|
||||
- WP-24c: Integration der EdgeRegistry zur dynamischen Inversions-Ermittlung.
|
||||
- WP-24c: Implementierung von validate_and_symmetrize für bidirektionale Graphen.
|
||||
- WP-25b: Beibehaltung der hierarchischen Prompt-Resolution und Modell-Spezi-Logik.
|
||||
AUDIT v2.12.3: Integration der zentralen Text-Bereinigung (WP-14).
|
||||
"""
|
||||
import logging
|
||||
from typing import Dict, Any, Optional, List
|
||||
from typing import Dict, Any
|
||||
from app.core.parser import NoteContext
|
||||
|
||||
# Import der neutralen Bereinigungs-Logik zur Vermeidung von Circular Imports
|
||||
# ENTSCHEIDENDER FIX: Import der neutralen Bereinigungs-Logik zur Vermeidung von Circular Imports
|
||||
from app.core.registry import clean_llm_text
|
||||
# WP-24c: Zugriff auf das dynamische Vokabular
|
||||
from app.services.edge_registry import registry as edge_registry
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -26,45 +17,40 @@ async def validate_edge_candidate(
|
|||
edge: Dict,
|
||||
batch_cache: Dict[str, NoteContext],
|
||||
llm_service: Any,
|
||||
provider: Optional[str] = None,
|
||||
profile_name: str = "ingest_validator"
|
||||
provider: str
|
||||
) -> bool:
|
||||
"""
|
||||
WP-15b/25b: Validiert einen Kandidaten semantisch gegen das Ziel im Cache.
|
||||
Nutzt Lazy-Prompt-Loading (PROMPT-TRACE) für deterministische YES/NO Entscheidungen.
|
||||
WP-15b: Validiert einen Kandidaten semantisch gegen das Ziel im Cache.
|
||||
Nutzt clean_llm_text zur Entfernung von Steuerzeichen vor der Auswertung.
|
||||
"""
|
||||
target_id = edge.get("to")
|
||||
target_ctx = batch_cache.get(target_id)
|
||||
|
||||
# Robust Lookup Fix (v2.12.2): Support für Anker (Note#Section)
|
||||
if not target_ctx and "#" in str(target_id):
|
||||
# Robust Lookup Fix (v2.12.2): Support für Anker
|
||||
if not target_ctx and "#" in target_id:
|
||||
base_id = target_id.split("#")[0]
|
||||
target_ctx = batch_cache.get(base_id)
|
||||
|
||||
# Sicherheits-Fallback (Hard-Link Integrity)
|
||||
# Wenn das Ziel nicht im Cache ist, erlauben wir die Kante (Link-Erhalt).
|
||||
if not target_ctx:
|
||||
logger.info(f"ℹ️ [VALIDATION SKIP] No context for '{target_id}' - allowing link.")
|
||||
return True
|
||||
|
||||
try:
|
||||
logger.info(f"⚖️ [VALIDATING] Relation '{edge.get('kind')}' -> '{target_id}' (Profile: {profile_name})...")
|
||||
template = llm_service.get_prompt("edge_validation", provider)
|
||||
|
||||
# WP-25b: Lazy-Prompt Aufruf.
|
||||
# Übergabe von prompt_key und Variablen für modell-optimierte Formatierung.
|
||||
raw_response = await llm_service.generate_raw_response(
|
||||
prompt_key="edge_validation",
|
||||
variables={
|
||||
"chunk_text": chunk_text[:1500],
|
||||
"target_title": target_ctx.title,
|
||||
"target_summary": target_ctx.summary,
|
||||
"edge_kind": edge.get("kind", "related_to")
|
||||
},
|
||||
priority="background",
|
||||
profile_name=profile_name
|
||||
try:
|
||||
logger.info(f"⚖️ [VALIDATING] Relation '{edge.get('kind')}' -> '{target_id}'...")
|
||||
prompt = template.format(
|
||||
chunk_text=chunk_text[:1500],
|
||||
target_title=target_ctx.title,
|
||||
target_summary=target_ctx.summary,
|
||||
edge_kind=edge.get("kind", "related_to")
|
||||
)
|
||||
|
||||
# Bereinigung zur Sicherstellung der Interpretierbarkeit (Mistral/Qwen Safe)
|
||||
# Die Antwort vom Service anfordern
|
||||
raw_response = await llm_service.generate_raw_response(prompt, priority="background")
|
||||
|
||||
# WP-14 Fix: Zusätzliche Bereinigung zur Sicherstellung der Interpretierbarkeit
|
||||
response = clean_llm_text(raw_response)
|
||||
|
||||
# Semantische Prüfung des Ergebnisses
|
||||
|
|
@ -75,76 +61,7 @@ async def validate_edge_candidate(
|
|||
else:
|
||||
logger.info(f"🚫 [REJECTED] Relation to '{target_id}' irrelevant for this chunk.")
|
||||
return is_valid
|
||||
|
||||
except Exception as e:
|
||||
error_str = str(e).lower()
|
||||
error_type = type(e).__name__
|
||||
|
||||
# WP-25b: Differenzierung zwischen transienten und permanenten Fehlern
|
||||
# Transiente Fehler (Netzwerk) → erlauben (Integrität vor Präzision)
|
||||
if any(x in error_str for x in ["timeout", "connection", "network", "unreachable", "refused"]):
|
||||
logger.warning(f"⚠️ Transient error for {target_id}: {error_type} - {e}. Allowing edge.")
|
||||
logger.warning(f"⚠️ Validation error for {target_id}: {e}")
|
||||
# Im Zweifel (Timeout/Fehler) erlauben wir die Kante, um Datenverlust zu vermeiden
|
||||
return True
|
||||
|
||||
# Permanente Fehler → ablehnen (Graph-Qualität schützen)
|
||||
logger.error(f"❌ Permanent validation error for {target_id}: {error_type} - {e}")
|
||||
return False
|
||||
|
||||
async def validate_and_symmetrize(
|
||||
chunk_text: str,
|
||||
edge: Dict,
|
||||
source_id: str,
|
||||
batch_cache: Dict[str, NoteContext],
|
||||
llm_service: Any,
|
||||
profile_name: str = "ingest_validator"
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
WP-24c: Erweitertes Validierungs-Gateway.
|
||||
Prüft die Primärkante und erzeugt bei Erfolg automatisch die inverse Kante.
|
||||
|
||||
Returns:
|
||||
List[Dict]: Eine Liste mit 0, 1 (nur Primär) oder 2 (Primär + Invers) Kanten.
|
||||
"""
|
||||
# 1. Semantische Prüfung der Primärkante (A -> B)
|
||||
is_valid = await validate_edge_candidate(
|
||||
chunk_text=chunk_text,
|
||||
edge=edge,
|
||||
batch_cache=batch_cache,
|
||||
llm_service=llm_service,
|
||||
profile_name=profile_name
|
||||
)
|
||||
|
||||
if not is_valid:
|
||||
return []
|
||||
|
||||
validated_edges = [edge]
|
||||
|
||||
# 2. WP-24c: Symmetrie-Generierung (B -> A)
|
||||
# Wir laden den inversen Typ dynamisch aus der EdgeRegistry (Single Source of Truth)
|
||||
original_kind = edge.get("kind", "related_to")
|
||||
inverse_kind = edge_registry.get_inverse(original_kind)
|
||||
|
||||
# Wir erzeugen eine inverse Kante nur, wenn ein sinnvoller inverser Typ existiert
|
||||
# und das Ziel der Primärkante (to) valide ist.
|
||||
target_id = edge.get("to")
|
||||
|
||||
if target_id and source_id:
|
||||
# Die inverse Kante zeigt vom Ziel der Primärkante zurück zur Quelle.
|
||||
# Sie wird als 'virtual' markiert, um sie im Retrieval/UI identifizierbar zu machen.
|
||||
inverse_edge = {
|
||||
"to": source_id,
|
||||
"kind": inverse_kind,
|
||||
"provenance": "structure", # System-generiert, geschützt durch Firewall
|
||||
"confidence": edge.get("confidence", 0.9) * 0.9, # Leichte Dämpfung für virtuelle Pfade
|
||||
"virtual": True,
|
||||
"note_id": target_id, # Die Note, von der die inverse Kante ausgeht
|
||||
"rule_id": f"symmetry:{original_kind}"
|
||||
}
|
||||
|
||||
# Wir fügen die Symmetrie nur hinzu, wenn sie einen echten Mehrwert bietet
|
||||
# (Vermeidung von redundanten related_to -> related_to Loops)
|
||||
if inverse_kind != original_kind or original_kind not in ["related_to", "references"]:
|
||||
validated_edges.append(inverse_edge)
|
||||
logger.info(f"🔄 [SYMMETRY] Generated inverse edge: '{target_id}' --({inverse_kind})--> '{source_id}'")
|
||||
|
||||
return validated_edges
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
import logging
|
||||
import os
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
def setup_logging(log_level: int = None):
|
||||
"""
|
||||
Konfiguriert das Logging-System mit File- und Console-Handler.
|
||||
WP-24c v4.4.0-DEBUG: Unterstützt DEBUG-Level für End-to-End Tracing.
|
||||
|
||||
Args:
|
||||
log_level: Optionales Log-Level (logging.DEBUG, logging.INFO, etc.)
|
||||
Falls nicht gesetzt, wird aus DEBUG Umgebungsvariable gelesen.
|
||||
"""
|
||||
# 1. Log-Level bestimmen
|
||||
if log_level is None:
|
||||
# WP-24c v4.4.0-DEBUG: Unterstützung für DEBUG-Level via Umgebungsvariable
|
||||
debug_mode = os.getenv("DEBUG", "false").lower() == "true"
|
||||
log_level = logging.DEBUG if debug_mode else logging.INFO
|
||||
|
||||
# 2. Log-Verzeichnis erstellen (falls nicht vorhanden)
|
||||
log_dir = "logs"
|
||||
if not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir)
|
||||
|
||||
log_file = os.path.join(log_dir, "mindnet.log")
|
||||
|
||||
# 3. Formatter definieren (Zeitstempel | Level | Modul | Nachricht)
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s | %(levelname)-8s | %(name)s | %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# 4. File Handler: Schreibt in Datei (max. 5MB pro Datei, behält 5 Backups)
|
||||
file_handler = RotatingFileHandler(
|
||||
log_file, maxBytes=5*1024*1024, backupCount=5, encoding='utf-8'
|
||||
)
|
||||
file_handler.setFormatter(formatter)
|
||||
file_handler.setLevel(log_level) # WP-24c v4.4.0-DEBUG: Respektiert log_level
|
||||
|
||||
# 5. Stream Handler: Schreibt weiterhin auf die Konsole
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(formatter)
|
||||
console_handler.setLevel(log_level) # WP-24c v4.4.0-DEBUG: Respektiert log_level
|
||||
|
||||
# 6. Root Logger konfigurieren
|
||||
logging.basicConfig(
|
||||
level=log_level,
|
||||
handlers=[file_handler, console_handler],
|
||||
force=True # Überschreibt bestehende Konfiguration
|
||||
)
|
||||
|
||||
level_name = "DEBUG" if log_level == logging.DEBUG else "INFO"
|
||||
logging.info(f"📝 Logging initialized (Level: {level_name}). Writing to {log_file}")
|
||||
22
app/core/qdrant.py
Normal file
22
app/core/qdrant.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
"""
|
||||
FILE: app/core/qdrant.py
|
||||
DESCRIPTION: Proxy-Modul zur Aufrechterhaltung der Abwärtskompatibilität (WP-14).
|
||||
Leitet alle Aufrufe an das neue database-Paket weiter.
|
||||
STATUS: Proxy (Legacy-Support)
|
||||
"""
|
||||
from .database.qdrant import (
|
||||
QdrantConfig,
|
||||
get_client,
|
||||
ensure_collections,
|
||||
ensure_payload_indexes,
|
||||
collection_names
|
||||
)
|
||||
|
||||
# Re-Export für 100% Kompatibilität
|
||||
__all__ = [
|
||||
"QdrantConfig",
|
||||
"get_client",
|
||||
"ensure_collections",
|
||||
"ensure_payload_indexes",
|
||||
"collection_names",
|
||||
]
|
||||
24
app/core/qdrant_points.py
Normal file
24
app/core/qdrant_points.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
"""
|
||||
FILE: app/core/qdrant_points.py
|
||||
DESCRIPTION: Proxy-Modul zur Aufrechterhaltung der Abwärtskompatibilität (WP-14).
|
||||
Leitet Point-Operationen an das neue database-Paket weiter.
|
||||
STATUS: Proxy (Legacy-Support)
|
||||
"""
|
||||
from .database.qdrant_points import (
|
||||
points_for_note,
|
||||
points_for_chunks,
|
||||
points_for_edges,
|
||||
upsert_batch,
|
||||
get_edges_for_sources,
|
||||
search_chunks_by_vector
|
||||
)
|
||||
|
||||
# Re-Export für 100% Kompatibilität
|
||||
__all__ = [
|
||||
"points_for_note",
|
||||
"points_for_chunks",
|
||||
"points_for_edges",
|
||||
"upsert_batch",
|
||||
"get_edges_for_sources",
|
||||
"search_chunks_by_vector"
|
||||
]
|
||||
|
|
@ -1,378 +0,0 @@
|
|||
"""
|
||||
FILE: app/core/retrieval/decision_engine.py
|
||||
DESCRIPTION: Der Agentic Orchestrator für MindNet (WP-25b Edition).
|
||||
Realisiert Multi-Stream Retrieval, Intent-basiertes Routing
|
||||
und die neue Lazy-Prompt Orchestrierung (Module A & B).
|
||||
VERSION: 1.3.2 (WP-25b: Full Robustness Recovery & Regex Parsing)
|
||||
STATUS: Active
|
||||
FIX:
|
||||
- WP-25b: ULTRA-Robustes Intent-Parsing via Regex (Fix: 'CODING[/S]' -> 'CODING').
|
||||
- WP-25b: Wiederherstellung der prepend_instruction Logik via variables.
|
||||
- WP-25a: Voller Erhalt der Profil-Kaskade via LLMService v3.5.5.
|
||||
- WP-25: Beibehaltung von Stream-Tracing, Edge-Boosts und Pre-Initialization.
|
||||
- RECOVERY: Wiederherstellung der lokalen Sicherheits-Gates aus v1.2.1.
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import yaml
|
||||
import os
|
||||
import re # Neu für robustes Intent-Parsing
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
# Core & Service Imports
|
||||
from app.models.dto import QueryRequest, QueryResponse
|
||||
from app.core.retrieval.retriever import Retriever
|
||||
from app.services.llm_service import LLMService
|
||||
from app.config import get_settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DecisionEngine:
|
||||
def __init__(self):
|
||||
"""Initialisiert die Engine und lädt die modularen Konfigurationen."""
|
||||
self.settings = get_settings()
|
||||
self.retriever = Retriever()
|
||||
self.llm_service = LLMService()
|
||||
self.config = self._load_engine_config()
|
||||
|
||||
def _load_engine_config(self) -> Dict[str, Any]:
|
||||
"""Lädt die Multi-Stream Konfiguration (WP-25/25a)."""
|
||||
path = os.getenv("MINDNET_DECISION_CONFIG", "config/decision_engine.yaml")
|
||||
if not os.path.exists(path):
|
||||
logger.error(f"❌ Decision Engine Config not found at {path}")
|
||||
return {"strategies": {}, "streams_library": {}}
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f) or {}
|
||||
|
||||
# WP-25b FIX: Schema-Validierung
|
||||
required_keys = ["strategies", "streams_library"]
|
||||
missing = [k for k in required_keys if k not in config]
|
||||
if missing:
|
||||
logger.error(f"❌ Missing required keys in decision_engine.yaml: {missing}")
|
||||
return {"strategies": {}, "streams_library": {}}
|
||||
|
||||
# Warnung bei unbekannten Top-Level-Keys
|
||||
known_keys = {"version", "settings", "strategies", "streams_library"}
|
||||
unknown = set(config.keys()) - known_keys
|
||||
if unknown:
|
||||
logger.warning(f"⚠️ Unknown keys in decision_engine.yaml: {unknown}")
|
||||
|
||||
logger.info(f"⚙️ Decision Engine Config loaded (v{config.get('version', 'unknown')})")
|
||||
return config
|
||||
except yaml.YAMLError as e:
|
||||
logger.error(f"❌ YAML syntax error in decision_engine.yaml: {e}")
|
||||
return {"strategies": {}, "streams_library": {}}
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to load decision_engine.yaml: {e}")
|
||||
return {"strategies": {}, "streams_library": {}}
|
||||
|
||||
async def ask(self, query: str) -> str:
|
||||
"""
|
||||
Hauptmethode des MindNet Chats.
|
||||
Orchestriert den agentischen Prozess: Routing -> Retrieval -> Kompression -> Synthese.
|
||||
"""
|
||||
# 1. Intent Recognition (Strategy Routing)
|
||||
strategy_key = await self._determine_strategy(query)
|
||||
|
||||
strategies = self.config.get("strategies", {})
|
||||
strategy = strategies.get(strategy_key)
|
||||
|
||||
if not strategy:
|
||||
logger.warning(f"⚠️ Unknown strategy '{strategy_key}'. Fallback to FACT_WHAT.")
|
||||
strategy_key = "FACT_WHAT"
|
||||
strategy = strategies.get("FACT_WHAT")
|
||||
|
||||
if not strategy and strategies:
|
||||
strategy_key = next(iter(strategies))
|
||||
strategy = strategies[strategy_key]
|
||||
|
||||
if not strategy:
|
||||
return "Entschuldigung, meine Wissensbasis ist aktuell nicht konfiguriert."
|
||||
|
||||
# 2. Multi-Stream Retrieval & Pre-Synthesis (Parallel Tasks inkl. Kompression)
|
||||
stream_results = await self._execute_parallel_streams(strategy, query)
|
||||
|
||||
# 3. Finale Synthese
|
||||
return await self._generate_final_answer(strategy_key, strategy, query, stream_results)
|
||||
|
||||
async def _determine_strategy(self, query: str) -> str:
|
||||
"""WP-25b: Nutzt den LLM-Router via Lazy-Loading und bereinigt Modell-Artefakte via Regex."""
|
||||
settings_cfg = self.config.get("settings", {})
|
||||
prompt_key = settings_cfg.get("router_prompt_key", "intent_router_v1")
|
||||
router_profile = settings_cfg.get("router_profile")
|
||||
|
||||
try:
|
||||
# Delegation an LLMService ohne manuelle Vor-Formatierung
|
||||
response = await self.llm_service.generate_raw_response(
|
||||
prompt_key=prompt_key,
|
||||
variables={"query": query},
|
||||
max_retries=1,
|
||||
priority="realtime",
|
||||
profile_name=router_profile
|
||||
)
|
||||
|
||||
# --- ULTRA-ROBUST PARSING (Fix für 'CODING[/S]') ---
|
||||
# 1. Alles in Großbuchstaben umwandeln
|
||||
raw_text = str(response).upper()
|
||||
|
||||
# 2. Regex: Suche das erste Wort, das nur aus A-Z und Unterstrichen besteht
|
||||
# Dies ignoriert [/S], </s>, Newlines oder Plaudereien des Modells
|
||||
match = re.search(r'\b(FACT_WHEN|FACT_WHAT|DECISION|EMPATHY|CODING|INTERVIEW)\b', raw_text)
|
||||
|
||||
if match:
|
||||
intent = match.group(1)
|
||||
logger.info(f"🎯 [ROUTING] Parsed Intent: '{intent}' from raw response: '{response.strip()}'")
|
||||
return intent
|
||||
|
||||
# Fallback, falls Regex nicht greift
|
||||
logger.warning(f"⚠️ Unmapped intent '{response.strip()}' from router. Falling back to FACT_WHAT.")
|
||||
return "FACT_WHAT"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Strategy Routing failed: {e}")
|
||||
return "FACT_WHAT"
|
||||
|
||||
async def _execute_parallel_streams(self, strategy: Dict, query: str) -> Dict[str, str]:
|
||||
"""Führt Such-Streams aus und komprimiert überlange Ergebnisse (Pre-Synthesis)."""
|
||||
stream_keys = strategy.get("use_streams", [])
|
||||
library = self.config.get("streams_library", {})
|
||||
|
||||
# Phase 1: Retrieval Tasks starten
|
||||
retrieval_tasks = []
|
||||
active_streams = []
|
||||
for key in stream_keys:
|
||||
stream_cfg = library.get(key)
|
||||
if stream_cfg:
|
||||
active_streams.append(key)
|
||||
retrieval_tasks.append(self._run_single_stream(key, stream_cfg, query))
|
||||
|
||||
# Ergebnisse sammeln
|
||||
retrieval_results = await asyncio.gather(*retrieval_tasks, return_exceptions=True)
|
||||
|
||||
# Phase 2: Formatierung und optionale Kompression
|
||||
# WP-24c v4.5.5: Context-Reuse - Sicherstellen, dass formatted_context auch bei Kompressions-Fehlern erhalten bleibt
|
||||
final_stream_tasks = []
|
||||
formatted_contexts = {} # WP-24c v4.5.5: Persistenz für Fallback-Zugriff
|
||||
|
||||
for name, res in zip(active_streams, retrieval_results):
|
||||
if isinstance(res, Exception):
|
||||
logger.error(f"Stream '{name}' failed during retrieval: {res}")
|
||||
error_msg = f"[Fehler im Wissens-Stream {name}]"
|
||||
formatted_contexts[name] = error_msg
|
||||
async def _err(msg=error_msg): return msg
|
||||
final_stream_tasks.append(_err())
|
||||
continue
|
||||
|
||||
formatted_context = self._format_stream_context(res)
|
||||
formatted_contexts[name] = formatted_context # WP-24c v4.5.5: Persistenz für Fallback
|
||||
|
||||
# WP-25a: Kompressions-Check (Inhaltsverdichtung)
|
||||
stream_cfg = library.get(name, {})
|
||||
threshold = stream_cfg.get("compression_threshold", 4000)
|
||||
|
||||
if len(formatted_context) > threshold:
|
||||
logger.info(f"⚙️ [WP-25b] Triggering Lazy-Compression for stream '{name}'...")
|
||||
comp_profile = stream_cfg.get("compression_profile")
|
||||
# WP-24c v4.5.5: Kompression mit Context-Reuse - bei Fehler wird formatted_context zurückgegeben
|
||||
final_stream_tasks.append(
|
||||
self._compress_stream_content(name, formatted_context, query, comp_profile)
|
||||
)
|
||||
else:
|
||||
async def _direct(c=formatted_context): return c
|
||||
final_stream_tasks.append(_direct())
|
||||
|
||||
# Finale Inhalte parallel fertigstellen
|
||||
# WP-24c v4.5.5: Bei Kompressions-Fehlern wird der Original-Content zurückgegeben (siehe _compress_stream_content)
|
||||
final_contents = await asyncio.gather(*final_stream_tasks, return_exceptions=True)
|
||||
|
||||
# WP-24c v4.5.5: Exception-Handling für finale Inhalte - verwende Original-Content bei Fehlern
|
||||
final_results = {}
|
||||
for name, content in zip(active_streams, final_contents):
|
||||
if isinstance(content, Exception):
|
||||
logger.warning(f"⚠️ [CONTEXT-REUSE] Stream '{name}' Fehler in finaler Verarbeitung: {content}. Verwende Original-Context.")
|
||||
final_results[name] = formatted_contexts.get(name, f"[Fehler im Stream {name}]")
|
||||
else:
|
||||
final_results[name] = content
|
||||
|
||||
logger.debug(f"📊 [STREAMS] Finale Stream-Ergebnisse: {[(k, len(v)) for k, v in final_results.items()]}")
|
||||
return final_results
|
||||
|
||||
async def _compress_stream_content(self, stream_name: str, content: str, query: str, profile: Optional[str]) -> str:
|
||||
"""
|
||||
WP-25b: Inhaltsverdichtung via Lazy-Loading 'compression_template'.
|
||||
WP-24c v4.5.5: Context-Reuse - Bei Fehlern wird der Original-Content zurückgegeben,
|
||||
um Re-Retrieval zu vermeiden.
|
||||
"""
|
||||
try:
|
||||
# WP-24c v4.5.5: Logging für LLM-Trace im Kompressions-Modus
|
||||
logger.debug(f"🔧 [COMPRESSION] Starte Kompression für Stream '{stream_name}' (Content-Länge: {len(content)})")
|
||||
|
||||
summary = await self.llm_service.generate_raw_response(
|
||||
prompt_key="compression_template",
|
||||
variables={
|
||||
"stream_name": stream_name,
|
||||
"content": content,
|
||||
"query": query
|
||||
},
|
||||
profile_name=profile,
|
||||
priority="background",
|
||||
max_retries=1
|
||||
)
|
||||
|
||||
# WP-24c v4.5.5: Validierung des Kompressions-Ergebnisses
|
||||
if summary and len(summary.strip()) > 10:
|
||||
logger.debug(f"✅ [COMPRESSION] Kompression erfolgreich für '{stream_name}' (Original: {len(content)}, Komprimiert: {len(summary)})")
|
||||
return summary.strip()
|
||||
else:
|
||||
logger.warning(f"⚠️ [COMPRESSION] Kompressions-Ergebnis zu kurz für '{stream_name}', verwende Original-Content")
|
||||
return content
|
||||
|
||||
except Exception as e:
|
||||
# WP-24c v4.5.5: Context-Reuse - Bei Fehlern Original-Content zurückgeben (kein Re-Retrieval)
|
||||
logger.error(f"❌ [COMPRESSION] Kompression von '{stream_name}' fehlgeschlagen: {e}")
|
||||
logger.info(f"🔄 [CONTEXT-REUSE] Verwende Original-Content für '{stream_name}' (Länge: {len(content)}) - KEIN Re-Retrieval")
|
||||
return content
|
||||
|
||||
async def _run_single_stream(self, name: str, cfg: Dict, query: str) -> QueryResponse:
|
||||
"""Spezialisierte Graph-Suche mit Stream-Tracing und Edge-Boosts."""
|
||||
transformed_query = cfg.get("query_template", "{query}").format(query=query)
|
||||
|
||||
request = QueryRequest(
|
||||
query=transformed_query,
|
||||
top_k=cfg.get("top_k", 5),
|
||||
filters={"type": cfg.get("filter_types", [])},
|
||||
expand={"depth": 1},
|
||||
boost_edges=cfg.get("edge_boosts", {}), # Erhalt der Gewichtung
|
||||
explain=True
|
||||
)
|
||||
|
||||
# WP-24c v4.5.0-DEBUG: Retrieval-Tracer - Protokollierung vor der Suche
|
||||
logger.info(f"🔍 [RETRIEVAL] Starte Stream: '{name}'")
|
||||
logger.info(f" -> Transformierte Query: '{transformed_query}'")
|
||||
logger.debug(f" ⚙️ [FILTER] Angewandte Metadaten-Filter: {request.filters}")
|
||||
logger.debug(f" ⚙️ [FILTER] Top-K: {request.top_k}, Expand-Depth: {request.expand.get('depth') if request.expand else None}")
|
||||
|
||||
response = await self.retriever.search(request)
|
||||
|
||||
# WP-24c v4.5.0-DEBUG: Retrieval-Tracer - Protokollierung nach der Suche
|
||||
if not response.results:
|
||||
logger.warning(f"⚠️ [EMPTY] Stream '{name}' lieferte 0 Ergebnisse.")
|
||||
else:
|
||||
logger.info(f"✨ [SUCCESS] Stream '{name}' lieferte {len(response.results)} Treffer.")
|
||||
# Top 3 Treffer im DEBUG-Level loggen
|
||||
# WP-24c v4.5.4: QueryHit hat kein chunk_id Feld - verwende node_id (enthält die Chunk-ID)
|
||||
for i, hit in enumerate(response.results[:3]):
|
||||
chunk_id = hit.node_id # node_id ist die Chunk-ID (pid)
|
||||
score = hit.total_score # QueryHit hat total_score, nicht score
|
||||
logger.debug(f" [{i+1}] Chunk: {chunk_id} | Score: {score:.4f} | Path: {hit.source.get('path', 'N/A') if hit.source else 'N/A'}")
|
||||
|
||||
for hit in response.results:
|
||||
hit.stream_origin = name
|
||||
return response
|
||||
|
||||
def _format_stream_context(self, response: QueryResponse) -> str:
|
||||
"""Wandelt QueryHits in einen formatierten Kontext-String um."""
|
||||
if not response.results:
|
||||
return "Keine spezifischen Informationen gefunden."
|
||||
lines = []
|
||||
for i, hit in enumerate(response.results, 1):
|
||||
source = hit.source.get("path", "Unbekannt")
|
||||
content = hit.source.get("text", "").strip()
|
||||
lines.append(f"[{i}] QUELLE: {source}\nINHALT: {content}")
|
||||
return "\n\n".join(lines)
|
||||
|
||||
async def _generate_final_answer(
|
||||
self,
|
||||
strategy_key: str,
|
||||
strategy: Dict,
|
||||
query: str,
|
||||
stream_results: Dict[str, str]
|
||||
) -> str:
|
||||
"""WP-25b: Finale Synthese via Lazy-Prompt mit Robustheit aus v1.2.1."""
|
||||
profile = strategy.get("llm_profile")
|
||||
template_key = strategy.get("prompt_template", "fact_synthesis_v1")
|
||||
system_prompt = self.llm_service.get_prompt("system_prompt")
|
||||
|
||||
# WP-25 ROBUSTNESS: Pre-Initialization der Variablen
|
||||
all_possible_streams = ["values_stream", "facts_stream", "biography_stream", "risk_stream", "tech_stream"]
|
||||
template_vars = {s: "" for s in all_possible_streams}
|
||||
template_vars.update(stream_results)
|
||||
template_vars["query"] = query
|
||||
|
||||
# WP-25a Erhalt: Prepend Instructions aus der strategy_config
|
||||
prepend = strategy.get("prepend_instruction", "")
|
||||
template_vars["prepend_instruction"] = prepend
|
||||
|
||||
try:
|
||||
# WP-25b: Delegation der Synthese an den LLMService
|
||||
response = await self.llm_service.generate_raw_response(
|
||||
prompt_key=template_key,
|
||||
variables=template_vars,
|
||||
system=system_prompt,
|
||||
profile_name=profile,
|
||||
priority="realtime"
|
||||
)
|
||||
|
||||
# WP-25a RECOVERY: Falls dieprepend_instruction nicht im Template-Key
|
||||
# der prompts.yaml enthalten ist (WP-25b Lazy Loading), fügen wir sie
|
||||
# hier manuell an den Anfang, um die Logik aus v1.2.1 zu bewahren.
|
||||
if prepend and prepend not in response[:len(prepend)+50]:
|
||||
logger.info("ℹ️ Adding prepend_instruction manually (not found in response).")
|
||||
response = f"{prepend}\n\n{response}"
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Final Synthesis failed: {e}")
|
||||
# WP-24c v4.5.5: ROBUST FALLBACK mit Context-Reuse
|
||||
# WICHTIG: stream_results werden Wiederverwendet - KEIN Re-Retrieval
|
||||
logger.info(f"🔄 [FALLBACK] Verwende vorhandene stream_results (KEIN Re-Retrieval)")
|
||||
logger.debug(f" -> Verfügbare Streams: {list(stream_results.keys())}")
|
||||
logger.debug(f" -> Stream-Längen: {[(k, len(v)) for k, v in stream_results.items()]}")
|
||||
|
||||
# WP-24c v4.5.5: Context-Reuse - Nutze vorhandene stream_results
|
||||
fallback_context = "\n\n".join([v for v in stream_results.values() if len(v) > 20])
|
||||
|
||||
if not fallback_context or len(fallback_context.strip()) < 20:
|
||||
logger.warning(f"⚠️ [FALLBACK] Fallback-Context zu kurz ({len(fallback_context)} Zeichen). Stream-Ergebnisse möglicherweise leer.")
|
||||
return f"Entschuldigung, ich konnte keine relevanten Informationen zu Ihrer Anfrage finden. (Fehler: {str(e)})"
|
||||
|
||||
try:
|
||||
# WP-24c v4.5.5: Fallback-Synthese mit LLM-Trace-Logging
|
||||
logger.info(f"🔄 [FALLBACK] Starte Fallback-Synthese mit vorhandenem Context (Länge: {len(fallback_context)})")
|
||||
logger.debug(f" -> Fallback-Profile: {profile}, Template: fallback_synthesis")
|
||||
|
||||
result = await self.llm_service.generate_raw_response(
|
||||
prompt_key="fallback_synthesis",
|
||||
variables={"query": query, "context": fallback_context},
|
||||
system=system_prompt, priority="realtime", profile_name=profile
|
||||
)
|
||||
|
||||
logger.info(f"✅ [FALLBACK] Fallback-Synthese erfolgreich (Antwort-Länge: {len(result) if result else 0})")
|
||||
return result
|
||||
|
||||
except (ValueError, KeyError) as template_error:
|
||||
# WP-24c v4.5.9: Fallback auf generisches Template mit variables
|
||||
# Nutzt Lazy-Loading aus WP-25b für modell-spezifische Fallback-Prompts
|
||||
logger.warning(f"⚠️ [FALLBACK] Template 'fallback_synthesis' nicht gefunden: {template_error}. Versuche generisches Template.")
|
||||
logger.debug(f" -> Fallback-Profile: {profile}, Context-Länge: {len(fallback_context)}")
|
||||
|
||||
try:
|
||||
# WP-24c v4.5.9: Versuche generisches Template mit variables (Lazy-Loading)
|
||||
result = await self.llm_service.generate_raw_response(
|
||||
prompt_key="fallback_synthesis_generic", # Fallback-Template
|
||||
variables={"query": query, "context": fallback_context},
|
||||
system=system_prompt, priority="realtime", profile_name=profile
|
||||
)
|
||||
logger.info(f"✅ [FALLBACK] Generisches Template erfolgreich (Antwort-Länge: {len(result) if result else 0})")
|
||||
return result
|
||||
except (ValueError, KeyError) as fallback_error:
|
||||
# WP-24c v4.5.9: Letzter Fallback - direkter Prompt (nur wenn beide Templates fehlen)
|
||||
logger.error(f"❌ [FALLBACK] Auch generisches Template nicht gefunden: {fallback_error}. Verwende direkten Prompt als letzten Fallback.")
|
||||
result = await self.llm_service.generate_raw_response(
|
||||
prompt=f"Beantworte: {query}\n\nKontext:\n{fallback_context}",
|
||||
system=system_prompt, priority="realtime", profile_name=profile
|
||||
)
|
||||
logger.info(f"✅ [FALLBACK] Direkter Prompt erfolgreich (Antwort-Länge: {len(result) if result else 0})")
|
||||
return result
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
"""
|
||||
FILE: app/core/retrieval/retriever.py
|
||||
DESCRIPTION: Haupt-Schnittstelle für die Suche. Orchestriert Vektorsuche und Graph-Expansion.
|
||||
WP-15c Update: Note-Level Diversity Pooling & Super-Edge Aggregation.
|
||||
WP-24c v4.1.0: Gold-Standard - Scope-Awareness, Section-Filtering, Authority-Priorisierung.
|
||||
VERSION: 0.8.0 (WP-24c: Gold-Standard v4.1.0)
|
||||
Nutzt retriever_scoring.py für die WP-22 Logik.
|
||||
MODULARISIERUNG: Verschoben in das retrieval-Paket für WP-14.
|
||||
VERSION: 0.6.16
|
||||
STATUS: Active
|
||||
DEPENDENCIES: app.config, app.models.dto, app.core.database*, app.core.graph_adapter
|
||||
"""
|
||||
|
|
@ -13,7 +13,6 @@ import os
|
|||
import time
|
||||
import logging
|
||||
from typing import Any, Dict, List, Tuple, Iterable, Optional
|
||||
from collections import defaultdict
|
||||
|
||||
from app.config import get_settings
|
||||
from app.models.dto import (
|
||||
|
|
@ -26,13 +25,10 @@ import app.core.database.qdrant as qdr
|
|||
import app.core.database.qdrant_points as qp
|
||||
|
||||
import app.services.embeddings_client as ec
|
||||
import app.core.graph.graph_subgraph as ga
|
||||
import app.core.graph.graph_db_adapter as gdb
|
||||
from app.core.graph.graph_utils import PROVENANCE_PRIORITY
|
||||
from qdrant_client.http import models as rest
|
||||
import app.core.graph_adapter as ga
|
||||
|
||||
# Mathematische Engine importieren
|
||||
from app.core.retrieval.retriever_scoring import get_weights, compute_wp22_score
|
||||
# Mathematische Engine importieren (Bleibt vorerst in app.core)
|
||||
from app.core.retriever_scoring import get_weights, compute_wp22_score
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -67,79 +63,15 @@ def _get_query_vector(req: QueryRequest) -> List[float]:
|
|||
return ec.embed_text(req.query)
|
||||
|
||||
|
||||
def _get_chunk_ids_for_notes(
|
||||
client: Any,
|
||||
prefix: str,
|
||||
note_ids: List[str]
|
||||
) -> List[str]:
|
||||
"""
|
||||
WP-24c v4.1.0: Lädt alle Chunk-IDs für gegebene Note-IDs.
|
||||
Wird für Scope-Aware Edge Retrieval benötigt.
|
||||
"""
|
||||
if not note_ids:
|
||||
return []
|
||||
|
||||
_, chunks_col, _ = qp._names(prefix)
|
||||
chunk_ids = []
|
||||
|
||||
try:
|
||||
# Filter: note_id IN note_ids
|
||||
note_filter = rest.Filter(should=[
|
||||
rest.FieldCondition(key="note_id", match=rest.MatchValue(value=str(nid)))
|
||||
for nid in note_ids
|
||||
])
|
||||
|
||||
pts, _ = client.scroll(
|
||||
collection_name=chunks_col,
|
||||
scroll_filter=note_filter,
|
||||
limit=2048,
|
||||
with_payload=True,
|
||||
with_vectors=False
|
||||
)
|
||||
|
||||
for pt in pts:
|
||||
pl = pt.payload or {}
|
||||
cid = pl.get("chunk_id")
|
||||
if cid:
|
||||
chunk_ids.append(str(cid))
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to load chunk IDs for notes: {e}")
|
||||
|
||||
return chunk_ids
|
||||
|
||||
def _semantic_hits(
|
||||
client: Any,
|
||||
prefix: str,
|
||||
vector: List[float],
|
||||
top_k: int,
|
||||
filters: Optional[Dict] = None,
|
||||
target_section: Optional[str] = None
|
||||
filters: Optional[Dict] = None
|
||||
) -> List[Tuple[str, float, Dict[str, Any]]]:
|
||||
"""
|
||||
Führt die Vektorsuche via database-Points-Modul durch.
|
||||
WP-24c v4.1.0: Unterstützt optionales Section-Filtering.
|
||||
"""
|
||||
# WP-24c v4.1.0: Section-Filtering für präzise Section-Links
|
||||
if target_section and filters:
|
||||
filters = {**filters, "section": target_section}
|
||||
elif target_section:
|
||||
filters = {"section": target_section}
|
||||
|
||||
"""Führt die Vektorsuche via database-Points-Modul durch."""
|
||||
raw_hits = qp.search_chunks_by_vector(client, prefix, vector, top=top_k, filters=filters)
|
||||
|
||||
# WP-24c v4.5.0-DEBUG: Retrieval-Tracer - Protokollierung der rohen Qdrant-Antwort
|
||||
logger.debug(f"📊 [RAW-HITS] Qdrant lieferte {len(raw_hits)} Roh-Treffer (Top-K: {top_k})")
|
||||
if filters:
|
||||
logger.debug(f" ⚙️ [FILTER] Angewandte Filter: {filters}")
|
||||
|
||||
# Logge die Top 3 Roh-Scores für Diagnose
|
||||
for i, hit in enumerate(raw_hits[:3]):
|
||||
hit_id = str(hit[0]) if hit else "N/A"
|
||||
hit_score = float(hit[1]) if hit and len(hit) > 1 else 0.0
|
||||
hit_payload = dict(hit[2] or {}) if hit and len(hit) > 2 else {}
|
||||
hit_path = hit_payload.get('path', 'N/A')
|
||||
logger.debug(f" [{i+1}] ID: {hit_id} | Raw-Score: {hit_score:.4f} | Path: {hit_path}")
|
||||
|
||||
# Strikte Typkonvertierung für Stabilität
|
||||
return [(str(hit[0]), float(hit[1]), dict(hit[2] or {})) for hit in raw_hits]
|
||||
|
||||
|
|
@ -157,6 +89,7 @@ def _build_explanation(
|
|||
) -> Explanation:
|
||||
"""
|
||||
Transformiert mathematische Scores und Graph-Signale in eine menschenlesbare Erklärung.
|
||||
Behebt Pydantic ValidationErrors durch explizite String-Sicherung.
|
||||
"""
|
||||
_, edge_w_cfg, _ = get_weights()
|
||||
base_val = scoring_debug["base_val"]
|
||||
|
|
@ -183,22 +116,12 @@ def _build_explanation(
|
|||
elif semantic_score > 0.70:
|
||||
reasons.append(Reason(kind="semantic", message="Inhaltliche Übereinstimmung.", score_impact=base_val))
|
||||
|
||||
# 3. Gründe für Typ und Lifecycle (WP-25 Vorbereitung)
|
||||
# 3. Gründe für Typ und Lifecycle
|
||||
type_weight = float(payload.get("retriever_weight", 1.0))
|
||||
if type_weight != 1.0:
|
||||
msg = "Bevorzugt" if type_weight > 1.0 else "De-priorisiert"
|
||||
reasons.append(Reason(kind="type", message=f"{msg} durch Typ-Profil.", score_impact=base_val * (type_weight - 1.0)))
|
||||
|
||||
# NEU: Explizite Ausweisung des Lifecycle-Status (WP-22)
|
||||
status_mult = scoring_debug.get("status_multiplier", 1.0)
|
||||
if status_mult != 1.0:
|
||||
status_msg = "Belohnt (Stable)" if status_mult > 1.0 else "De-priorisiert (Draft)"
|
||||
reasons.append(Reason(
|
||||
kind="status",
|
||||
message=f"{status_msg} durch Content-Lifecycle.",
|
||||
score_impact=semantic_score * (status_mult - 1.0)
|
||||
))
|
||||
|
||||
# 4. Kanten-Verarbeitung (Graph-Intelligence)
|
||||
if subgraph and target_note_id and scoring_debug["edge_bonus"] > 0:
|
||||
raw_edges = []
|
||||
|
|
@ -208,6 +131,7 @@ def _build_explanation(
|
|||
raw_edges.extend(subgraph.get_outgoing_edges(target_note_id) or [])
|
||||
|
||||
for edge in raw_edges:
|
||||
# FIX: Zwingende String-Konvertierung für Pydantic-Stabilität
|
||||
src = str(edge.get("source") or "note_root")
|
||||
tgt = str(edge.get("target") or target_note_id or "unknown_target")
|
||||
kind = str(edge.get("kind", "related_to"))
|
||||
|
|
@ -216,9 +140,6 @@ def _build_explanation(
|
|||
|
||||
direction = "in" if tgt == target_note_id else "out"
|
||||
|
||||
# WP-24c v4.5.10: Robuste EdgeDTO-Erstellung mit Fehlerbehandlung
|
||||
# Falls Provenance-Wert nicht unterstützt wird, verwende Fallback
|
||||
try:
|
||||
edge_obj = EdgeDTO(
|
||||
id=f"{src}->{tgt}:{kind}",
|
||||
kind=kind,
|
||||
|
|
@ -230,35 +151,12 @@ def _build_explanation(
|
|||
confidence=conf
|
||||
)
|
||||
edges_dto.append(edge_obj)
|
||||
except Exception as e:
|
||||
# WP-24c v4.5.10: Fallback bei Validierungsfehler (z.B. alte EdgeDTO-Version im Cache)
|
||||
logger.warning(
|
||||
f"⚠️ [EDGE-DTO] Provenance '{prov}' nicht unterstützt für Edge {src}->{tgt} ({kind}). "
|
||||
f"Fehler: {e}. Verwende Fallback 'explicit'."
|
||||
)
|
||||
# Fallback: Verwende 'explicit' als sicheren Default
|
||||
try:
|
||||
edge_obj = EdgeDTO(
|
||||
id=f"{src}->{tgt}:{kind}",
|
||||
kind=kind,
|
||||
source=src,
|
||||
target=tgt,
|
||||
weight=conf,
|
||||
direction=direction,
|
||||
provenance="explicit", # Fallback
|
||||
confidence=conf
|
||||
)
|
||||
edges_dto.append(edge_obj)
|
||||
except Exception as e2:
|
||||
logger.error(f"❌ [EDGE-DTO] Auch Fallback fehlgeschlagen: {e2}. Überspringe Edge.")
|
||||
# Überspringe diese Kante - besser als kompletter Fehler
|
||||
|
||||
# Die 3 wichtigsten Kanten als Begründung formulieren
|
||||
top_edges = sorted(edges_dto, key=lambda e: e.confidence, reverse=True)
|
||||
for e in top_edges[:3]:
|
||||
peer = e.source if e.direction == "in" else e.target
|
||||
# WP-24c v4.5.3: Unterstütze alle explicit-Varianten (explicit, explicit:callout, etc.)
|
||||
prov_txt = "Bestätigte" if e.provenance and e.provenance.startswith("explicit") else "KI-basierte"
|
||||
prov_txt = "Bestätigte" if e.provenance == "explicit" else "KI-basierte"
|
||||
boost_txt = f" [Boost x{applied_boosts.get(e.kind)}]" if applied_boosts and e.kind in applied_boosts else ""
|
||||
|
||||
reasons.append(Reason(
|
||||
|
|
@ -289,14 +187,10 @@ def _build_hits_from_semantic(
|
|||
explain: bool = False,
|
||||
dynamic_edge_boosts: Dict[str, float] = None
|
||||
) -> QueryResponse:
|
||||
"""
|
||||
Wandelt semantische Roh-Treffer in bewertete QueryHits um.
|
||||
WP-15c: Implementiert Note-Level Diversity Pooling.
|
||||
"""
|
||||
"""Wandelt semantische Roh-Treffer in bewertete QueryHits um."""
|
||||
t0 = time.time()
|
||||
enriched = []
|
||||
|
||||
# Erstes Scoring für alle Kandidaten
|
||||
for pid, semantic_score, payload in hits:
|
||||
edge_bonus, cent_bonus = 0.0, 0.0
|
||||
target_id = payload.get("note_id")
|
||||
|
|
@ -308,30 +202,15 @@ def _build_hits_from_semantic(
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# Mathematisches Scoring via WP-22 Engine
|
||||
debug_data = compute_wp22_score(
|
||||
semantic_score, payload, edge_bonus, cent_bonus, dynamic_edge_boosts
|
||||
)
|
||||
enriched.append((pid, semantic_score, payload, debug_data))
|
||||
|
||||
# 1. Sortierung nach finalem mathematischen Score
|
||||
# Sortierung nach finalem mathematischen Score
|
||||
enriched_sorted = sorted(enriched, key=lambda h: h[3]["total"], reverse=True)
|
||||
|
||||
# 2. WP-15c: Note-Level Diversity Pooling
|
||||
# Wir behalten pro note_id nur den Hit mit dem höchsten total_score.
|
||||
# Dies verhindert, dass 10 Chunks derselben Note andere KeyNotes verdrängen.
|
||||
unique_note_hits = []
|
||||
seen_notes = set()
|
||||
|
||||
for item in enriched_sorted:
|
||||
_, _, payload, _ = item
|
||||
note_id = str(payload.get("note_id", "unknown"))
|
||||
|
||||
if note_id not in seen_notes:
|
||||
unique_note_hits.append(item)
|
||||
seen_notes.add(note_id)
|
||||
|
||||
# 3. Begrenzung auf top_k nach dem Diversity-Pooling
|
||||
limited_hits = unique_note_hits[: max(1, top_k)]
|
||||
limited_hits = enriched_sorted[: max(1, top_k)]
|
||||
|
||||
results: List[QueryHit] = []
|
||||
for pid, s_score, pl, dbg in limited_hits:
|
||||
|
|
@ -346,18 +225,9 @@ def _build_hits_from_semantic(
|
|||
applied_boosts=dynamic_edge_boosts
|
||||
)
|
||||
|
||||
# Payload Text-Feld normalisieren
|
||||
text_content = pl.get("page_content") or pl.get("text") or pl.get("content", "[Kein Text]")
|
||||
|
||||
# WP-24c v4.1.0: RAG-Kontext - source_chunk_id aus Edge-Payload extrahieren
|
||||
source_chunk_id = None
|
||||
if explanation_obj and explanation_obj.related_edges:
|
||||
# Finde die erste Edge mit chunk_id als source
|
||||
for edge in explanation_obj.related_edges:
|
||||
# Prüfe, ob source eine Chunk-ID ist (enthält # oder ist chunk_id)
|
||||
if edge.source and ("#" in edge.source or edge.source.startswith("chunk:")):
|
||||
source_chunk_id = edge.source
|
||||
break
|
||||
|
||||
results.append(QueryHit(
|
||||
node_id=str(pid),
|
||||
note_id=str(pl.get("note_id", "unknown")),
|
||||
|
|
@ -371,51 +241,23 @@ def _build_hits_from_semantic(
|
|||
"text": text_content
|
||||
},
|
||||
payload=pl,
|
||||
explanation=explanation_obj,
|
||||
source_chunk_id=source_chunk_id # WP-24c v4.1.0: RAG-Kontext
|
||||
explanation=explanation_obj
|
||||
))
|
||||
|
||||
# WP-24c v4.5.0-DEBUG: Retrieval-Tracer - Finale Ergebnisse
|
||||
latency_ms = int((time.time() - t0) * 1000)
|
||||
if not results:
|
||||
logger.warning(f"⚠️ [EMPTY] Hybride Suche lieferte 0 Ergebnisse (Latency: {latency_ms}ms)")
|
||||
else:
|
||||
logger.info(f"✨ [SUCCESS] Hybride Suche lieferte {len(results)} Treffer (Latency: {latency_ms}ms)")
|
||||
# Top 3 finale Scores loggen
|
||||
# WP-24c v4.5.4: QueryHit hat kein chunk_id Feld - verwende node_id (enthält die Chunk-ID)
|
||||
for i, hit in enumerate(results[:3]):
|
||||
chunk_id = hit.node_id # node_id ist die Chunk-ID (pid)
|
||||
logger.debug(f" [{i+1}] Final: Chunk={chunk_id} | Total-Score={hit.total_score:.4f} | Semantic={hit.semantic_score:.4f} | Edge={hit.edge_bonus:.4f}")
|
||||
|
||||
return QueryResponse(results=results, used_mode=used_mode, latency_ms=latency_ms)
|
||||
return QueryResponse(results=results, used_mode=used_mode, latency_ms=int((time.time() - t0) * 1000))
|
||||
|
||||
|
||||
def hybrid_retrieve(req: QueryRequest) -> QueryResponse:
|
||||
"""
|
||||
Die Haupt-Einstiegsfunktion für die hybride Suche.
|
||||
WP-15c: Implementiert Edge-Aggregation (Super-Kanten).
|
||||
WP-24c v4.5.0-DEBUG: Retrieval-Tracer für Diagnose.
|
||||
Kombiniert Vektorsuche mit Graph-Expansion und WP-22 Gewichtung.
|
||||
"""
|
||||
# WP-24c v4.5.0-DEBUG: Retrieval-Tracer - Start der hybriden Suche
|
||||
logger.info(f"🔍 [RETRIEVAL] Starte hybride Suche")
|
||||
logger.info(f" -> Query: '{req.query[:100]}...' (Länge: {len(req.query)})")
|
||||
logger.debug(f" ⚙️ [FILTER] Request-Filter: {req.filters}")
|
||||
logger.debug(f" ⚙️ [FILTER] Top-K: {req.top_k}, Expand: {req.expand}, Target-Section: {req.target_section}")
|
||||
client, prefix = _get_client_and_prefix()
|
||||
vector = list(req.query_vector) if req.query_vector else _get_query_vector(req)
|
||||
top_k = req.top_k or 10
|
||||
|
||||
# 1. Semantische Seed-Suche (Wir laden etwas mehr für das Pooling)
|
||||
# WP-24c v4.1.0: Section-Filtering unterstützen
|
||||
target_section = getattr(req, "target_section", None)
|
||||
|
||||
# WP-24c v4.5.0-DEBUG: Retrieval-Tracer - Vor semantischer Suche
|
||||
logger.debug(f"🔍 [RETRIEVAL] Starte semantische Seed-Suche (Top-K: {top_k * 3}, Target-Section: {target_section})")
|
||||
|
||||
hits = _semantic_hits(client, prefix, vector, top_k=top_k * 3, filters=req.filters, target_section=target_section)
|
||||
|
||||
# WP-24c v4.5.0-DEBUG: Retrieval-Tracer - Nach semantischer Suche
|
||||
logger.debug(f"📊 [SEED-HITS] Semantische Suche lieferte {len(hits)} Seed-Treffer")
|
||||
# 1. Semantische Seed-Suche
|
||||
hits = _semantic_hits(client, prefix, vector, top_k=top_k, filters=req.filters)
|
||||
|
||||
# 2. Graph Expansion Konfiguration
|
||||
expand_cfg = req.expand if isinstance(req.expand, dict) else {}
|
||||
|
|
@ -424,157 +266,39 @@ def hybrid_retrieve(req: QueryRequest) -> QueryResponse:
|
|||
|
||||
subgraph: ga.Subgraph | None = None
|
||||
if depth > 0 and hits:
|
||||
# WP-24c v4.5.2: Chunk-Aware Graph Traversal
|
||||
# Extrahiere sowohl note_id als auch chunk_id (pid) direkt aus den Hits
|
||||
# Dies stellt sicher, dass Chunk-Scope Edges gefunden werden
|
||||
seed_note_ids = list({h[2].get("note_id") for h in hits if h[2].get("note_id")})
|
||||
seed_chunk_ids = list({h[0] for h in hits if h[0]}) # pid ist die Chunk-ID
|
||||
# Start-IDs für den Graph-Traversal sammeln
|
||||
seed_ids = list({h[2].get("note_id") for h in hits if h[2].get("note_id")})
|
||||
|
||||
# Kombiniere beide Sets für vollständige Seed-Abdeckung
|
||||
# Chunk-IDs können auch als Note-IDs fungieren (für Note-Scope Edges)
|
||||
all_seed_ids = list(set(seed_note_ids + seed_chunk_ids))
|
||||
|
||||
if all_seed_ids:
|
||||
if seed_ids:
|
||||
try:
|
||||
# WP-24c v4.5.2: Chunk-IDs sind bereits aus Hits extrahiert
|
||||
# Zusätzlich können wir noch weitere Chunk-IDs für die Note-IDs laden
|
||||
# (für den Fall, dass nicht alle Chunks in den Top-K Hits sind)
|
||||
additional_chunk_ids = _get_chunk_ids_for_notes(client, prefix, seed_note_ids)
|
||||
# Kombiniere direkte Chunk-IDs aus Hits mit zusätzlich geladenen
|
||||
all_chunk_ids = list(set(seed_chunk_ids + additional_chunk_ids))
|
||||
# Subgraph aus RAM/DB laden
|
||||
subgraph = ga.expand(client, prefix, seed_ids, depth=depth, edge_types=expand_cfg.get("edge_types"))
|
||||
|
||||
# WP-24c v4.5.2: Erweiterte Edge-Retrieval mit Chunk-Scope und Section-Filtering
|
||||
# Verwende all_seed_ids (enthält sowohl note_id als auch chunk_id)
|
||||
# und all_chunk_ids für explizite Chunk-Scope Edge-Suche
|
||||
subgraph = ga.expand(
|
||||
client, prefix, all_seed_ids,
|
||||
depth=depth,
|
||||
edge_types=expand_cfg.get("edge_types"),
|
||||
chunk_ids=all_chunk_ids,
|
||||
target_section=target_section
|
||||
)
|
||||
# --- WP-22: Kanten-Gewichtung im RAM-Graphen vor Bonus-Berechnung ---
|
||||
if subgraph and hasattr(subgraph, "graph"):
|
||||
for _, _, data in subgraph.graph.edges(data=True):
|
||||
# A. Provenance Weighting (WP-22 Bonus für Herkunft)
|
||||
prov = data.get("provenance", "rule")
|
||||
# Belohnung: Explizite Links (1.0) > Smart (0.9) > Rule (0.7)
|
||||
prov_w = 1.0 if prov == "explicit" else (0.9 if prov == "smart" else 0.7)
|
||||
|
||||
# WP-24c v4.5.2: Debug-Logging für Chunk-Awareness
|
||||
logger.debug(f"🔍 [SEEDS] Note-IDs: {len(seed_note_ids)}, Chunk-IDs: {len(seed_chunk_ids)}, Total Seeds: {len(all_seed_ids)}")
|
||||
logger.debug(f" -> Zusätzliche Chunk-IDs geladen: {len(additional_chunk_ids)}, Total Chunk-IDs: {len(all_chunk_ids)}")
|
||||
|
||||
# --- WP-24c v4.1.0: Chunk-Level Edge-Aggregation & Deduplizierung ---
|
||||
# Verhindert Score-Explosion durch multiple Links auf versch. Abschnitte.
|
||||
# Logik: 1. Kante zählt voll, weitere dämpfen auf Faktor 0.1.
|
||||
# Erweitert um Chunk-Level Tracking für präzise In-Degree-Berechnung.
|
||||
if subgraph and hasattr(subgraph, "adj"):
|
||||
# WP-24c v4.1.0: Chunk-Level In-Degree Tracking
|
||||
chunk_level_in_degree = defaultdict(int) # target -> count of chunk sources
|
||||
|
||||
for src, edge_list in subgraph.adj.items():
|
||||
# Gruppiere Kanten nach Ziel-Note (Deduplizierung ID_A -> ID_B)
|
||||
by_target = defaultdict(list)
|
||||
for e in edge_list:
|
||||
by_target[e["target"]].append(e)
|
||||
|
||||
# WP-24c v4.1.0: Chunk-Level In-Degree Tracking
|
||||
# Wenn source eine Chunk-ID ist, zähle für Chunk-Level In-Degree
|
||||
if e.get("chunk_id") or (src and ("#" in src or src.startswith("chunk:"))):
|
||||
chunk_level_in_degree[e["target"]] += 1
|
||||
|
||||
aggregated_list = []
|
||||
for tgt, edges in by_target.items():
|
||||
if len(edges) > 1:
|
||||
# Sortiere: Stärkste Kante zuerst (Authority-Priorisierung)
|
||||
sorted_edges = sorted(
|
||||
edges,
|
||||
key=lambda x: (
|
||||
x.get("weight", 0.0) *
|
||||
(1.0 if not x.get("virtual", False) else 0.5) * # Virtual-Penalty
|
||||
float(x.get("confidence", 1.0)) # Confidence-Boost
|
||||
),
|
||||
reverse=True
|
||||
)
|
||||
primary = sorted_edges[0]
|
||||
|
||||
# Aggregiertes Gewicht berechnen (Sättigungs-Logik)
|
||||
total_w = primary.get("weight", 0.0)
|
||||
chunk_count = 0
|
||||
for secondary in sorted_edges[1:]:
|
||||
total_w += secondary.get("weight", 0.0) * 0.1
|
||||
if secondary.get("chunk_id") or (secondary.get("source") and ("#" in secondary.get("source", "") or secondary.get("source", "").startswith("chunk:"))):
|
||||
chunk_count += 1
|
||||
|
||||
primary["weight"] = total_w
|
||||
primary["is_super_edge"] = True # Flag für Explanation Layer
|
||||
primary["edge_count"] = len(edges)
|
||||
primary["chunk_source_count"] = chunk_count + (1 if (primary.get("chunk_id") or (primary.get("source") and ("#" in primary.get("source", "") or primary.get("source", "").startswith("chunk:")))) else 0)
|
||||
aggregated_list.append(primary)
|
||||
else:
|
||||
edge = edges[0]
|
||||
# WP-24c v4.1.0: Chunk-Count auch für einzelne Edges
|
||||
if edge.get("chunk_id") or (edge.get("source") and ("#" in edge.get("source", "") or edge.get("source", "").startswith("chunk:"))):
|
||||
edge["chunk_source_count"] = 1
|
||||
aggregated_list.append(edge)
|
||||
|
||||
# In-Place Update der Adjazenzliste des Graphen
|
||||
subgraph.adj[src] = aggregated_list
|
||||
|
||||
# Re-Sync der In-Degrees für Centrality-Bonus (Aggregation konsistent halten)
|
||||
subgraph.in_degree = defaultdict(int)
|
||||
for src, edges in subgraph.adj.items():
|
||||
for e in edges:
|
||||
subgraph.in_degree[e["target"]] += 1
|
||||
|
||||
# WP-24c v4.1.0: Chunk-Level In-Degree als Attribut speichern
|
||||
subgraph.chunk_level_in_degree = chunk_level_in_degree
|
||||
|
||||
# --- WP-24c v4.1.0: Authority-Priorisierung (Provenance & Confidence) ---
|
||||
if subgraph and hasattr(subgraph, "adj"):
|
||||
for src, edges in subgraph.adj.items():
|
||||
for e in edges:
|
||||
# A. Provenance Weighting (nutzt PROVENANCE_PRIORITY aus graph_utils)
|
||||
prov = e.get("provenance", "rule")
|
||||
prov_key = f"{prov}:{e.get('kind', 'related_to')}" if ":" not in prov else prov
|
||||
prov_w = PROVENANCE_PRIORITY.get(prov_key, PROVENANCE_PRIORITY.get(prov, 0.7))
|
||||
|
||||
# B. Confidence-Weighting (aus Edge-Payload)
|
||||
confidence = float(e.get("confidence", 1.0))
|
||||
|
||||
# C. Virtual-Flag De-Priorisierung
|
||||
is_virtual = e.get("virtual", False)
|
||||
virtual_penalty = 0.5 if is_virtual else 1.0
|
||||
|
||||
# D. Intent Boost Multiplikator
|
||||
kind = e.get("kind")
|
||||
# B. Intent Boost Multiplikator (Vom Router dynamisch injiziert)
|
||||
kind = data.get("kind")
|
||||
intent_multiplier = boost_edges.get(kind, 1.0)
|
||||
|
||||
# Gewichtung anpassen (Authority-Priorisierung)
|
||||
e["weight"] = e.get("weight", 1.0) * prov_w * confidence * virtual_penalty * intent_multiplier
|
||||
# Finales Gewicht setzen (Basis * Provenance * Intent)
|
||||
data["weight"] = data.get("weight", 1.0) * prov_w * intent_multiplier
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Graph Expansion failed: {e}")
|
||||
subgraph = None
|
||||
|
||||
# 3. Scoring & Explanation Generierung
|
||||
# top_k wird erst hier final angewandt
|
||||
# WP-24c v4.5.0-DEBUG: Retrieval-Tracer - Vor finaler Hit-Erstellung
|
||||
if subgraph:
|
||||
# WP-24c v4.5.1: Subgraph hat kein .edges Attribut, sondern .adj (Adjazenzliste)
|
||||
# Zähle alle Kanten aus der Adjazenzliste
|
||||
edge_count = sum(len(edges) for edges in subgraph.adj.values()) if hasattr(subgraph, 'adj') else 0
|
||||
logger.debug(f"📊 [GRAPH] Subgraph enthält {edge_count} Kanten")
|
||||
else:
|
||||
logger.debug(f"📊 [GRAPH] Kein Subgraph (depth=0 oder keine Seed-IDs)")
|
||||
|
||||
result = _build_hits_from_semantic(hits, top_k, "hybrid", subgraph, req.explain, boost_edges)
|
||||
|
||||
# WP-24c v4.5.0-DEBUG: Retrieval-Tracer - Nach finaler Hit-Erstellung
|
||||
if not result.results:
|
||||
logger.warning(f"⚠️ [EMPTY] Hybride Suche lieferte nach Scoring 0 finale Ergebnisse")
|
||||
else:
|
||||
logger.info(f"✨ [SUCCESS] Hybride Suche lieferte {len(result.results)} finale Treffer (Mode: {result.used_mode})")
|
||||
|
||||
return result
|
||||
return _build_hits_from_semantic(hits, top_k, "hybrid", subgraph, req.explain, boost_edges)
|
||||
|
||||
|
||||
def semantic_retrieve(req: QueryRequest) -> QueryResponse:
|
||||
"""Standard Vektorsuche ohne Graph-Einfluss."""
|
||||
"""Standard Vektorsuche ohne Graph-Einfluss (WP-02 Fallback)."""
|
||||
client, prefix = _get_client_and_prefix()
|
||||
vector = _get_query_vector(req)
|
||||
hits = _semantic_hits(client, prefix, vector, req.top_k or 10, req.filters)
|
||||
|
|
@ -584,4 +308,5 @@ def semantic_retrieve(req: QueryRequest) -> QueryResponse:
|
|||
class Retriever:
|
||||
"""Schnittstelle für die asynchrone Suche."""
|
||||
async def search(self, request: QueryRequest) -> QueryResponse:
|
||||
"""Führt eine hybride Suche aus."""
|
||||
return hybrid_retrieve(request)
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
"""
|
||||
FILE: app/core/retrieval/retriever_scoring.py
|
||||
DESCRIPTION: Mathematische Kern-Logik für das WP-22/WP-15c Scoring.
|
||||
DESCRIPTION: Mathematische Kern-Logik für das WP-22 Scoring.
|
||||
Berechnet Relevanz-Scores basierend auf Semantik, Graph-Intelligence und Content Lifecycle.
|
||||
FIX v1.0.3: Optimierte Interaktion zwischen Typ-Boost und Status-Dämpfung.
|
||||
VERSION: 1.0.3
|
||||
MODULARISIERUNG: Verschoben in das retrieval-Paket für WP-14.
|
||||
VERSION: 1.0.2
|
||||
STATUS: Active
|
||||
DEPENDENCIES: app.config, typing
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
|
|
@ -22,6 +23,10 @@ logger = logging.getLogger(__name__)
|
|||
def get_weights() -> Tuple[float, float, float]:
|
||||
"""
|
||||
Liefert die Basis-Gewichtung (semantic, edge, centrality) aus der Konfiguration.
|
||||
Priorität:
|
||||
1. config/retriever.yaml (Scoring-Sektion)
|
||||
2. Umgebungsvariablen (RETRIEVER_W_*)
|
||||
3. System-Defaults (1.0, 0.0, 0.0)
|
||||
"""
|
||||
from app.config import get_settings
|
||||
settings = get_settings()
|
||||
|
|
@ -53,7 +58,7 @@ def get_status_multiplier(payload: Dict[str, Any]) -> float:
|
|||
|
||||
- stable: 1.2 (Belohnung für verifiziertes Wissen)
|
||||
- active: 1.0 (Standard-Gewichtung)
|
||||
- draft: 0.5 (Dämpfung für unfertige Fragmente)
|
||||
- draft: 0.5 (Bestrafung für unfertige Fragmente)
|
||||
"""
|
||||
status = str(payload.get("status", "active")).lower().strip()
|
||||
if status == "stable":
|
||||
|
|
@ -70,58 +75,46 @@ def compute_wp22_score(
|
|||
dynamic_edge_boosts: Optional[Dict[str, float]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Die zentrale mathematische Scoring-Formel (WP-15c optimiert).
|
||||
Implementiert das Hybrid-Scoring (Semantic * Lifecycle * Graph).
|
||||
Die zentrale mathematische Scoring-Formel der Mindnet Intelligence.
|
||||
Implementiert das WP-22 Hybrid-Scoring (Semantic * Lifecycle * Graph).
|
||||
|
||||
LOGIK:
|
||||
1. Base = Similarity * StatusMult (Lifecycle-Filter).
|
||||
2. Boosts = (TypeBoost - 1) + (GraphBoni * IntentFactor).
|
||||
3. Final = Base * (1 + Boosts).
|
||||
FORMEL:
|
||||
Score = (Similarity * StatusMult) * (1 + (TypeWeight - 1) + ((EdgeW * EB + CentW * CB) * IntentBoost))
|
||||
|
||||
Der edge_bonus_raw enthält bereits die Super-Edge-Aggregation (WP-15c).
|
||||
Returns:
|
||||
Dict mit dem finalen 'total' Score und allen mathematischen Zwischenwerten für den Explanation Layer.
|
||||
"""
|
||||
sem_w, edge_w_cfg, cent_w_cfg = get_weights()
|
||||
status_mult = get_status_multiplier(payload)
|
||||
|
||||
# Retriever Weight (Typ-Boost aus types.yaml, z.B. 1.1 für Decisions)
|
||||
# Retriever Weight (Type Boost aus types.yaml, z.B. 1.1 für Decisions)
|
||||
node_weight = float(payload.get("retriever_weight", 1.0))
|
||||
|
||||
# 1. Berechnung des Base Scores (Semantik gewichtet durch Lifecycle-Status)
|
||||
# WICHTIG: Der Status wirkt hier als Multiplikator auf die Basis-Relevanz.
|
||||
base_val = float(semantic_score) * status_mult
|
||||
|
||||
# 2. Graph Boost Factor (Intent-spezifische Verstärkung aus decision_engine.yaml)
|
||||
# 2. Graph Boost Factor (Teil C: Intent-spezifische Verstärkung)
|
||||
# Erhöht das Gewicht des gesamten Graphen um 50%, wenn ein spezifischer Intent vorliegt.
|
||||
graph_boost_factor = 1.5 if dynamic_edge_boosts and (edge_bonus_raw > 0 or cent_bonus_raw > 0) else 1.0
|
||||
|
||||
# 3. Einzelne Graph-Komponenten berechnen
|
||||
# WP-15c Hinweis: edge_bonus_raw ist durch den retriever.py bereits gedämpft/aggregiert.
|
||||
edge_impact_final = (edge_w_cfg * edge_bonus_raw) * graph_boost_factor
|
||||
cent_impact_final = (cent_w_cfg * cent_bonus_raw) * graph_boost_factor
|
||||
|
||||
# 4. Finales Zusammenführen (Merging)
|
||||
# (node_weight - 1.0) wandelt das Gewicht in einen relativen Bonus um (z.B. 1.2 -> +0.2).
|
||||
# Alle Boni werden addiert und wirken dann auf den base_val.
|
||||
type_impact = node_weight - 1.0
|
||||
total_boost = 1.0 + type_impact + edge_impact_final + cent_impact_final
|
||||
|
||||
total = base_val * total_boost
|
||||
# (node_weight - 1.0) sorgt dafür, dass ein Gewicht von 1.0 keinen Einfluss hat (neutral).
|
||||
total = base_val * (1.0 + (node_weight - 1.0) + edge_impact_final + cent_impact_final)
|
||||
|
||||
# Sicherstellen, dass der Score niemals 0 oder negativ ist (Floor)
|
||||
final_score = max(0.0001, float(total))
|
||||
|
||||
# WP-24c v4.5.0-DEBUG: Retrieval-Tracer - Protokollierung der Score-Berechnung
|
||||
chunk_id = payload.get("chunk_id", payload.get("id", "unknown"))
|
||||
logger.debug(f"📈 [SCORE-TRACE] Chunk: {chunk_id} | Base: {base_val:.4f} | Multiplier: {total_boost:.2f} | Final: {final_score:.4f}")
|
||||
logger.debug(f" -> Details: StatusMult={status_mult:.2f}, TypeImpact={type_impact:.2f}, EdgeImpact={edge_impact_final:.4f}, CentImpact={cent_impact_final:.4f}")
|
||||
|
||||
return {
|
||||
"total": final_score,
|
||||
"edge_bonus": float(edge_bonus_raw),
|
||||
"cent_bonus": float(cent_bonus_raw),
|
||||
"status_multiplier": status_mult,
|
||||
"graph_boost_factor": graph_boost_factor,
|
||||
"type_impact": type_impact,
|
||||
"type_impact": node_weight - 1.0,
|
||||
"base_val": base_val,
|
||||
"edge_impact_final": edge_impact_final,
|
||||
"cent_impact_final": cent_impact_final
|
||||
|
|
|
|||
14
app/core/retriever.py
Normal file
14
app/core/retriever.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"""
|
||||
FILE: app/core/retriever.py
|
||||
DESCRIPTION: Proxy-Modul zur Aufrechterhaltung der Abwärtskompatibilität (WP-14).
|
||||
Leitet Retrieval-Anfragen an das neue retrieval-Paket weiter.
|
||||
STATUS: Proxy (Legacy-Support)
|
||||
"""
|
||||
from .retrieval.retriever import (
|
||||
Retriever,
|
||||
hybrid_retrieve,
|
||||
semantic_retrieve
|
||||
)
|
||||
|
||||
# Re-Export für 100% Kompatibilität
|
||||
__all__ = ["Retriever", "hybrid_retrieve", "semantic_retrieve"]
|
||||
18
app/core/retriever_scoring.py
Normal file
18
app/core/retriever_scoring.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
FILE: app/core/retriever_scoring.py
|
||||
DESCRIPTION: Proxy-Modul zur Aufrechterhaltung der Abwärtskompatibilität (WP-14).
|
||||
Leitet Scoring-Berechnungen an das neue retrieval-Paket weiter.
|
||||
STATUS: Proxy (Legacy-Support)
|
||||
"""
|
||||
from .retrieval.retriever_scoring import (
|
||||
get_weights,
|
||||
compute_wp22_score,
|
||||
get_status_multiplier
|
||||
)
|
||||
|
||||
# Re-Export für 100% Kompatibilität
|
||||
__all__ = [
|
||||
"get_weights",
|
||||
"compute_wp22_score",
|
||||
"get_status_multiplier"
|
||||
]
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
"""
|
||||
FILE: app/core/type_registry.py
|
||||
DESCRIPTION: Loader für types.yaml.
|
||||
WP-24c: Robustheits-Fix für chunking_profile vs chunk_profile.
|
||||
WP-14: Support für zentrale Registry-Strukturen.
|
||||
VERSION: 1.1.0 (Audit-Fix: Profile Key Consistency)
|
||||
STATUS: Active (Support für Legacy-Loader)
|
||||
DESCRIPTION: Loader für types.yaml. Achtung: Wird in der aktuellen Pipeline meist durch lokale Loader in 'ingestion.py' oder 'note_payload.py' umgangen.
|
||||
VERSION: 1.0.0
|
||||
STATUS: Deprecated (Redundant)
|
||||
DEPENDENCIES: yaml, os, functools
|
||||
EXTERNAL_CONFIG: config/types.yaml
|
||||
LAST_ANALYSIS: 2025-12-15
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -19,12 +18,12 @@ try:
|
|||
except Exception:
|
||||
yaml = None # wird erst benötigt, wenn eine Datei gelesen werden soll
|
||||
|
||||
# Konservativer Default – WP-24c: Nutzt nun konsistent 'chunking_profile'
|
||||
# Konservativer Default – bewusst minimal
|
||||
_DEFAULT_REGISTRY: Dict[str, Any] = {
|
||||
"version": "1.0",
|
||||
"types": {
|
||||
"concept": {
|
||||
"chunking_profile": "medium",
|
||||
"chunk_profile": "medium",
|
||||
"edge_defaults": ["references", "related_to"],
|
||||
"retriever_weight": 1.0,
|
||||
}
|
||||
|
|
@ -34,6 +33,7 @@ _DEFAULT_REGISTRY: Dict[str, Any] = {
|
|||
}
|
||||
|
||||
# Chunk-Profile → Overlap-Empfehlungen (nur für synthetische Fensterbildung)
|
||||
# Die absoluten Chunk-Längen bleiben Aufgabe des Chunkers (assemble_chunks).
|
||||
_PROFILE_TO_OVERLAP: Dict[str, Tuple[int, int]] = {
|
||||
"short": (20, 30),
|
||||
"medium": (40, 60),
|
||||
|
|
@ -45,7 +45,7 @@ _PROFILE_TO_OVERLAP: Dict[str, Tuple[int, int]] = {
|
|||
def load_type_registry(path: str = "config/types.yaml") -> Dict[str, Any]:
|
||||
"""
|
||||
Lädt die Registry aus 'path'. Bei Fehlern wird ein konserviver Default geliefert.
|
||||
Die Rückgabe ist prozessweit gecached.
|
||||
Die Rückgabe ist *prozessweit* gecached.
|
||||
"""
|
||||
if not path:
|
||||
return dict(_DEFAULT_REGISTRY)
|
||||
|
|
@ -54,6 +54,7 @@ def load_type_registry(path: str = "config/types.yaml") -> Dict[str, Any]:
|
|||
return dict(_DEFAULT_REGISTRY)
|
||||
|
||||
if yaml is None:
|
||||
# PyYAML fehlt → auf Default zurückfallen
|
||||
return dict(_DEFAULT_REGISTRY)
|
||||
|
||||
try:
|
||||
|
|
@ -70,7 +71,6 @@ def load_type_registry(path: str = "config/types.yaml") -> Dict[str, Any]:
|
|||
|
||||
|
||||
def get_type_config(note_type: Optional[str], reg: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Extrahiert die Konfiguration für einen spezifischen Typ."""
|
||||
t = (note_type or "concept").strip().lower()
|
||||
types = (reg or {}).get("types", {}) if isinstance(reg, dict) else {}
|
||||
return types.get(t) or types.get("concept") or _DEFAULT_REGISTRY["types"]["concept"]
|
||||
|
|
@ -84,13 +84,8 @@ def resolve_note_type(fm_type: Optional[str], reg: Dict[str, Any]) -> str:
|
|||
|
||||
|
||||
def effective_chunk_profile(note_type: Optional[str], reg: Dict[str, Any]) -> Optional[str]:
|
||||
"""
|
||||
Ermittelt das aktive Chunking-Profil für einen Notiz-Typ.
|
||||
Fix (Audit-Problem 2): Prüft beide Key-Varianten für 100% Kompatibilität.
|
||||
"""
|
||||
cfg = get_type_config(note_type, reg)
|
||||
# Check 'chunking_profile' (Standard) OR 'chunk_profile' (Legacy/Fallback)
|
||||
prof = cfg.get("chunking_profile") or cfg.get("chunk_profile")
|
||||
prof = cfg.get("chunk_profile")
|
||||
if isinstance(prof, str) and prof.strip():
|
||||
return prof.strip().lower()
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -69,20 +69,12 @@ def render_graph_explorer_cytoscape(graph_service):
|
|||
search_term = st.text_input("Suche Notiz", placeholder="Titel eingeben...", key="cy_search")
|
||||
|
||||
if search_term:
|
||||
try:
|
||||
hits, _ = graph_service.client.scroll(
|
||||
collection_name=f"{COLLECTION_PREFIX}_notes",
|
||||
limit=10,
|
||||
scroll_filter=models.Filter(must=[models.FieldCondition(key="title", match=models.MatchText(text=search_term))])
|
||||
)
|
||||
options = {}
|
||||
for h in hits:
|
||||
if h.payload and 'title' in h.payload and 'note_id' in h.payload:
|
||||
title = h.payload['title']
|
||||
note_id = h.payload['note_id']
|
||||
# Vermeide Duplikate (falls mehrere Chunks/Notes denselben Titel haben)
|
||||
if title not in options:
|
||||
options[title] = note_id
|
||||
options = {h.payload['title']: h.payload['note_id'] for h in hits}
|
||||
|
||||
if options:
|
||||
selected_title = st.selectbox("Ergebnisse:", list(options.keys()), key="cy_select")
|
||||
|
|
@ -91,13 +83,6 @@ def render_graph_explorer_cytoscape(graph_service):
|
|||
st.session_state.graph_center_id = new_id
|
||||
st.session_state.graph_inspected_id = new_id
|
||||
st.rerun()
|
||||
else:
|
||||
# Zeige Info, wenn keine Ergebnisse gefunden wurden
|
||||
st.info(f"Keine Notizen mit '{search_term}' im Titel gefunden.")
|
||||
except Exception as e:
|
||||
st.error(f"Fehler bei der Suche: {e}")
|
||||
import traceback
|
||||
st.code(traceback.format_exc())
|
||||
|
||||
st.divider()
|
||||
|
||||
|
|
@ -154,26 +139,6 @@ def render_graph_explorer_cytoscape(graph_service):
|
|||
# 2. Detail Daten (Inspector)
|
||||
inspected_data = graph_service.get_note_with_full_content(inspected_id)
|
||||
|
||||
# DEBUG: Zeige Debug-Informationen
|
||||
with st.expander("🔍 Debug-Informationen", expanded=False):
|
||||
st.write(f"**Gefundene Knoten:** {len(nodes_data) if nodes_data else 0}")
|
||||
st.write(f"**Gefundene Kanten:** {len(edges_data) if edges_data else 0}")
|
||||
if nodes_data:
|
||||
st.write("**Knoten-IDs:**")
|
||||
for n in nodes_data[:10]:
|
||||
nid = getattr(n, 'id', 'N/A')
|
||||
st.write(f" - {nid}")
|
||||
if len(nodes_data) > 10:
|
||||
st.write(f" ... und {len(nodes_data) - 10} weitere")
|
||||
if edges_data:
|
||||
st.write("**Kanten:**")
|
||||
for e in edges_data[:10]:
|
||||
src = getattr(e, "source", "N/A")
|
||||
tgt = getattr(e, "to", getattr(e, "target", "N/A"))
|
||||
st.write(f" - {src} -> {tgt}")
|
||||
if len(edges_data) > 10:
|
||||
st.write(f" ... und {len(edges_data) - 10} weitere")
|
||||
|
||||
# --- ACTION BAR ---
|
||||
action_container = st.container()
|
||||
with action_container:
|
||||
|
|
@ -209,12 +174,7 @@ def render_graph_explorer_cytoscape(graph_service):
|
|||
st.markdown(f"**ID:** `{inspected_data.get('note_id')}`")
|
||||
st.markdown(f"**Typ:** `{inspected_data.get('type')}`")
|
||||
with col_i2:
|
||||
tags = inspected_data.get('tags', [])
|
||||
if isinstance(tags, list):
|
||||
tags_str = ', '.join(tags) if tags else "Keine"
|
||||
else:
|
||||
tags_str = str(tags) if tags else "Keine"
|
||||
st.markdown(f"**Tags:** {tags_str}")
|
||||
st.markdown(f"**Tags:** {', '.join(inspected_data.get('tags', []))}")
|
||||
path_check = "✅" if inspected_data.get('path') else "❌"
|
||||
st.markdown(f"**Pfad:** {path_check}")
|
||||
|
||||
|
|
@ -229,27 +189,12 @@ def render_graph_explorer_cytoscape(graph_service):
|
|||
# --- GRAPH ELEMENTS ---
|
||||
cy_elements = []
|
||||
|
||||
# Validierung: Prüfe ob nodes_data vorhanden ist
|
||||
if not nodes_data:
|
||||
st.warning("⚠️ Keine Knoten gefunden. Bitte wähle eine andere Notiz.")
|
||||
# Zeige trotzdem den Inspector, falls Daten vorhanden
|
||||
if inspected_data:
|
||||
st.info(f"**Hinweis:** Die Notiz '{inspected_data.get('title', inspected_id)}' wurde gefunden, hat aber keine Verbindungen im Graphen.")
|
||||
return
|
||||
|
||||
# Erstelle Set aller Node-IDs für schnelle Validierung
|
||||
node_ids = {n.id for n in nodes_data if hasattr(n, 'id') and n.id}
|
||||
|
||||
# Nodes hinzufügen
|
||||
for n in nodes_data:
|
||||
if not hasattr(n, 'id') or not n.id:
|
||||
continue
|
||||
|
||||
is_center = (n.id == center_id)
|
||||
is_inspected = (n.id == inspected_id)
|
||||
|
||||
tooltip_text = getattr(n, 'title', None) or getattr(n, 'label', '')
|
||||
display_label = getattr(n, 'label', str(n.id))
|
||||
tooltip_text = n.title if n.title else n.label
|
||||
display_label = n.label
|
||||
if len(display_label) > 15 and " " in display_label:
|
||||
display_label = display_label.replace(" ", "\n", 1)
|
||||
|
||||
|
|
@ -257,7 +202,7 @@ def render_graph_explorer_cytoscape(graph_service):
|
|||
"data": {
|
||||
"id": n.id,
|
||||
"label": display_label,
|
||||
"bg_color": getattr(n, 'color', '#8395a7'),
|
||||
"bg_color": n.color,
|
||||
"tooltip": tooltip_text
|
||||
},
|
||||
# Wir steuern das Aussehen rein über Klassen (.inspected / .center)
|
||||
|
|
@ -266,19 +211,15 @@ def render_graph_explorer_cytoscape(graph_service):
|
|||
}
|
||||
cy_elements.append(cy_node)
|
||||
|
||||
# Edges hinzufügen - nur wenn beide Nodes im Graph vorhanden sind
|
||||
if edges_data:
|
||||
for e in edges_data:
|
||||
source_id = getattr(e, "source", None)
|
||||
target_id = getattr(e, "to", getattr(e, "target", None))
|
||||
# Nur hinzufügen, wenn beide IDs vorhanden UND beide Nodes im Graph sind
|
||||
if source_id and target_id and source_id in node_ids and target_id in node_ids:
|
||||
if target_id:
|
||||
cy_edge = {
|
||||
"data": {
|
||||
"source": source_id,
|
||||
"source": e.source,
|
||||
"target": target_id,
|
||||
"label": getattr(e, "label", ""),
|
||||
"line_color": getattr(e, "color", "#bdc3c7")
|
||||
"label": e.label,
|
||||
"line_color": e.color
|
||||
}
|
||||
}
|
||||
cy_elements.append(cy_edge)
|
||||
|
|
@ -351,10 +292,6 @@ def render_graph_explorer_cytoscape(graph_service):
|
|||
]
|
||||
|
||||
# --- RENDER ---
|
||||
# Nur rendern, wenn Elemente vorhanden sind
|
||||
if not cy_elements:
|
||||
st.warning("⚠️ Keine Graph-Elemente zum Anzeigen gefunden.")
|
||||
else:
|
||||
graph_key = f"cy_{center_id}_{st.session_state.cy_depth}_{st.session_state.cy_ideal_edge_len}"
|
||||
|
||||
clicked_elements = cytoscape(
|
||||
|
|
|
|||
|
|
@ -10,19 +10,15 @@ LAST_ANALYSIS: 2025-12-15
|
|||
import re
|
||||
from qdrant_client import QdrantClient, models
|
||||
from streamlit_agraph import Node, Edge
|
||||
from ui_config import COLLECTION_PREFIX, GRAPH_COLORS, get_edge_color, SYSTEM_EDGES
|
||||
from ui_config import GRAPH_COLORS, get_edge_color, SYSTEM_EDGES
|
||||
|
||||
class GraphExplorerService:
|
||||
def __init__(self, url, api_key=None, prefix=None):
|
||||
"""
|
||||
Initialisiert den Service. Nutzt COLLECTION_PREFIX aus der Config,
|
||||
sofern kein spezifischer Prefix übergeben wurde.
|
||||
"""
|
||||
def __init__(self, url, api_key=None, prefix="mindnet"):
|
||||
self.client = QdrantClient(url=url, api_key=api_key)
|
||||
self.prefix = prefix if prefix else COLLECTION_PREFIX
|
||||
self.notes_col = f"{self.prefix}_notes"
|
||||
self.chunks_col = f"{self.prefix}_chunks"
|
||||
self.edges_col = f"{self.prefix}_edges"
|
||||
self.prefix = prefix
|
||||
self.notes_col = f"{prefix}_notes"
|
||||
self.chunks_col = f"{prefix}_chunks"
|
||||
self.edges_col = f"{prefix}_edges"
|
||||
self._note_cache = {}
|
||||
|
||||
def get_note_with_full_content(self, note_id):
|
||||
|
|
@ -167,32 +163,30 @@ class GraphExplorerService:
|
|||
return previews
|
||||
|
||||
def _find_connected_edges(self, note_ids, note_title=None):
|
||||
"""
|
||||
Findet eingehende und ausgehende Kanten.
|
||||
"""Findet eingehende und ausgehende Kanten."""
|
||||
|
||||
WICHTIG: target_id enthält nur den Titel (ohne #Abschnitt).
|
||||
target_section ist ein separates Feld für Abschnitt-Informationen.
|
||||
"""
|
||||
results = []
|
||||
if not note_ids:
|
||||
return results
|
||||
|
||||
# 1. OUTGOING EDGES (Der "Owner"-Fix)
|
||||
# Wir suchen Kanten, die im Feld 'note_id' (Owner) eine unserer Notizen haben.
|
||||
# Das findet ALLE ausgehenden Kanten, egal ob sie an einem Chunk oder der Note hängen.
|
||||
if note_ids:
|
||||
out_filter = models.Filter(must=[
|
||||
models.FieldCondition(key="note_id", match=models.MatchAny(any=note_ids)),
|
||||
models.FieldCondition(key="kind", match=models.MatchExcept(**{"except": SYSTEM_EDGES}))
|
||||
])
|
||||
res_out, _ = self.client.scroll(self.edges_col, scroll_filter=out_filter, limit=2000, with_payload=True)
|
||||
# Limit hoch, um alles zu finden
|
||||
res_out, _ = self.client.scroll(self.edges_col, scroll_filter=out_filter, limit=500, with_payload=True)
|
||||
results.extend(res_out)
|
||||
|
||||
# 2. INCOMING EDGES (Ziel = Chunk ID, Note ID oder Titel)
|
||||
# WICHTIG: target_id enthält nur den Titel, target_section ist separat
|
||||
# 2. INCOMING EDGES (Ziel = Chunk ID oder Titel oder Note ID)
|
||||
# Hier müssen wir Chunks auflösen, um Treffer auf Chunks zu finden.
|
||||
|
||||
# Chunk IDs der aktuellen Notes holen
|
||||
chunk_ids = []
|
||||
if note_ids:
|
||||
c_filter = models.Filter(must=[models.FieldCondition(key="note_id", match=models.MatchAny(any=note_ids))])
|
||||
chunks, _ = self.client.scroll(self.chunks_col, scroll_filter=c_filter, limit=1000, with_payload=False)
|
||||
chunks, _ = self.client.scroll(self.chunks_col, scroll_filter=c_filter, limit=300)
|
||||
chunk_ids = [c.id for c in chunks]
|
||||
|
||||
shoulds = []
|
||||
|
|
@ -201,92 +195,42 @@ class GraphExplorerService:
|
|||
shoulds.append(models.FieldCondition(key="target_id", match=models.MatchAny(any=chunk_ids)))
|
||||
|
||||
# Case B: Edge zeigt direkt auf unsere Note ID
|
||||
if note_ids:
|
||||
shoulds.append(models.FieldCondition(key="target_id", match=models.MatchAny(any=note_ids)))
|
||||
|
||||
# Case C: Edge zeigt auf unseren Titel
|
||||
# WICHTIG: target_id enthält nur den Titel (z.B. "Meine Prinzipien 2025")
|
||||
# target_section enthält die Abschnitt-Information (z.B. "P3 – Disziplin"), wenn gesetzt
|
||||
|
||||
# Sammle alle relevanten Titel (inkl. Aliase)
|
||||
titles_to_search = []
|
||||
# Case C: Edge zeigt auf unseren Titel (Wikilinks)
|
||||
if note_title:
|
||||
titles_to_search.append(note_title)
|
||||
|
||||
# Lade auch Titel aus den Notes selbst (falls note_title nicht übergeben wurde)
|
||||
for nid in note_ids:
|
||||
note = self._fetch_note_cached(nid)
|
||||
if note:
|
||||
note_title_from_db = note.get("title")
|
||||
if note_title_from_db and note_title_from_db not in titles_to_search:
|
||||
titles_to_search.append(note_title_from_db)
|
||||
# Aliase hinzufügen
|
||||
aliases = note.get("aliases", [])
|
||||
if isinstance(aliases, str):
|
||||
aliases = [aliases]
|
||||
for alias in aliases:
|
||||
if alias and alias not in titles_to_search:
|
||||
titles_to_search.append(alias)
|
||||
|
||||
# Für jeden Titel: Suche nach exaktem Match
|
||||
# target_id enthält nur den Titel, daher reicht MatchValue
|
||||
for title in titles_to_search:
|
||||
shoulds.append(models.FieldCondition(key="target_id", match=models.MatchValue(value=title)))
|
||||
shoulds.append(models.FieldCondition(key="target_id", match=models.MatchValue(value=note_title)))
|
||||
|
||||
if shoulds:
|
||||
in_filter = models.Filter(
|
||||
must=[models.FieldCondition(key="kind", match=models.MatchExcept(**{"except": SYSTEM_EDGES}))],
|
||||
should=shoulds
|
||||
)
|
||||
res_in, _ = self.client.scroll(self.edges_col, scroll_filter=in_filter, limit=2000, with_payload=True)
|
||||
res_in, _ = self.client.scroll(self.edges_col, scroll_filter=in_filter, limit=500, with_payload=True)
|
||||
results.extend(res_in)
|
||||
|
||||
return results
|
||||
|
||||
def _find_connected_edges_batch(self, note_ids):
|
||||
"""
|
||||
Wrapper für Level 2 Suche.
|
||||
Lädt Titel der ersten Note für Titel-basierte Suche.
|
||||
"""
|
||||
if not note_ids:
|
||||
return []
|
||||
first_note = self._fetch_note_cached(note_ids[0])
|
||||
note_title = first_note.get("title") if first_note else None
|
||||
return self._find_connected_edges(note_ids, note_title=note_title)
|
||||
# Wrapper für Level 2 Suche
|
||||
return self._find_connected_edges(note_ids)
|
||||
|
||||
def _process_edge(self, record, nodes_dict, unique_edges, current_depth):
|
||||
"""
|
||||
Verarbeitet eine rohe Edge, löst IDs auf und fügt sie den Dictionaries hinzu.
|
||||
|
||||
WICHTIG: Beide Richtungen werden unterstützt:
|
||||
- Ausgehende Kanten: source_id gehört zu unserer Note (via note_id Owner)
|
||||
- Eingehende Kanten: target_id zeigt auf unsere Note (via target_id Match)
|
||||
"""
|
||||
if not record or not record.payload:
|
||||
return None, None
|
||||
|
||||
"""Verarbeitet eine rohe Edge, löst IDs auf und fügt sie den Dictionaries hinzu."""
|
||||
payload = record.payload
|
||||
src_ref = payload.get("source_id")
|
||||
tgt_ref = payload.get("target_id")
|
||||
kind = payload.get("kind")
|
||||
provenance = payload.get("provenance", "explicit")
|
||||
|
||||
# Prüfe, ob beide Referenzen vorhanden sind
|
||||
if not src_ref or not tgt_ref:
|
||||
return None, None
|
||||
|
||||
# IDs zu Notes auflösen
|
||||
# WICHTIG: source_id kann Chunk-ID (note_id#c01), Note-ID oder Titel sein
|
||||
# WICHTIG: target_id kann Chunk-ID, Note-ID oder Titel sein (ohne #Abschnitt)
|
||||
src_note = self._resolve_note_from_ref(src_ref)
|
||||
tgt_note = self._resolve_note_from_ref(tgt_ref)
|
||||
|
||||
if src_note and tgt_note:
|
||||
src_id = src_note.get('note_id')
|
||||
tgt_id = tgt_note.get('note_id')
|
||||
|
||||
# Prüfe, ob beide IDs vorhanden sind
|
||||
if not src_id or not tgt_id:
|
||||
return None, None
|
||||
src_id = src_note['note_id']
|
||||
tgt_id = tgt_note['note_id']
|
||||
|
||||
if src_id != tgt_id:
|
||||
# Nodes hinzufügen
|
||||
|
|
@ -301,7 +245,7 @@ class GraphExplorerService:
|
|||
# Bevorzuge explizite Kanten vor Smart Kanten
|
||||
is_current_explicit = (provenance in ["explicit", "rule"])
|
||||
if existing:
|
||||
is_existing_explicit = (existing.get('provenance', '') in ["explicit", "rule"])
|
||||
is_existing_explicit = (existing['provenance'] in ["explicit", "rule"])
|
||||
if is_existing_explicit and not is_current_explicit:
|
||||
should_update = False
|
||||
|
||||
|
|
@ -323,109 +267,38 @@ class GraphExplorerService:
|
|||
return None
|
||||
|
||||
def _resolve_note_from_ref(self, ref_str):
|
||||
"""
|
||||
Löst eine Referenz zu einer Note Payload auf.
|
||||
"""Löst eine ID (Chunk, Note oder Titel) zu einer Note Payload auf."""
|
||||
if not ref_str: return None
|
||||
|
||||
WICHTIG: Wenn ref_str ein Titel#Abschnitt Format hat, wird nur der Titel-Teil verwendet.
|
||||
Unterstützt:
|
||||
- Note-ID: "20250101-meine-note"
|
||||
- Chunk-ID: "20250101-meine-note#c01"
|
||||
- Titel: "Meine Prinzipien 2025"
|
||||
- Titel#Abschnitt: "Meine Prinzipien 2025#P3 – Disziplin" (trennt Abschnitt ab, sucht nur nach Titel)
|
||||
"""
|
||||
if not ref_str:
|
||||
return None
|
||||
|
||||
# Fall A: Enthält # (kann Chunk-ID oder Titel#Abschnitt sein)
|
||||
# Fall A: Chunk ID (enthält #)
|
||||
if "#" in ref_str:
|
||||
try:
|
||||
# Versuch 1: Chunk ID direkt (Format: note_id#c01)
|
||||
# Versuch 1: Chunk ID direkt
|
||||
res = self.client.retrieve(self.chunks_col, ids=[ref_str], with_payload=True)
|
||||
if res and res[0].payload:
|
||||
note_id = res[0].payload.get("note_id")
|
||||
if note_id:
|
||||
return self._fetch_note_cached(note_id)
|
||||
except:
|
||||
pass
|
||||
if res: return self._fetch_note_cached(res[0].payload.get("note_id"))
|
||||
except: pass
|
||||
|
||||
# Versuch 2: NoteID#Section (Hash abtrennen und als Note-ID versuchen)
|
||||
# z.B. "20250101-meine-note#Abschnitt" -> "20250101-meine-note"
|
||||
possible_note_id = ref_str.split("#")[0].strip()
|
||||
note = self._fetch_note_cached(possible_note_id)
|
||||
if note:
|
||||
return note
|
||||
|
||||
# Versuch 3: Titel#Abschnitt (Hash abtrennen und als Titel suchen)
|
||||
# z.B. "Meine Prinzipien 2025#P3 – Disziplin" -> "Meine Prinzipien 2025"
|
||||
# WICHTIG: target_id enthält nur den Titel, daher suchen wir nur nach dem Titel-Teil
|
||||
possible_title = ref_str.split("#")[0].strip()
|
||||
if possible_title:
|
||||
res, _ = self.client.scroll(
|
||||
collection_name=self.notes_col,
|
||||
scroll_filter=models.Filter(must=[
|
||||
models.FieldCondition(key="title", match=models.MatchValue(value=possible_title))
|
||||
]),
|
||||
limit=1, with_payload=True
|
||||
)
|
||||
if res and res[0].payload:
|
||||
payload = res[0].payload
|
||||
self._note_cache[payload['note_id']] = payload
|
||||
return payload
|
||||
|
||||
# Fallback: Text-Suche für Fuzzy-Matching
|
||||
res, _ = self.client.scroll(
|
||||
collection_name=self.notes_col,
|
||||
scroll_filter=models.Filter(must=[
|
||||
models.FieldCondition(key="title", match=models.MatchText(text=possible_title))
|
||||
]),
|
||||
limit=10, with_payload=True
|
||||
)
|
||||
if res:
|
||||
# Nimm das erste Ergebnis, das exakt oder beginnend mit possible_title übereinstimmt
|
||||
for r in res:
|
||||
if r.payload:
|
||||
note_title = r.payload.get("title", "")
|
||||
if note_title == possible_title or note_title.startswith(possible_title):
|
||||
payload = r.payload
|
||||
self._note_cache[payload['note_id']] = payload
|
||||
return payload
|
||||
# Versuch 2: NoteID#Section (Hash abtrennen)
|
||||
possible_note_id = ref_str.split("#")[0]
|
||||
if self._fetch_note_cached(possible_note_id): return self._fetch_note_cached(possible_note_id)
|
||||
|
||||
# Fall B: Note ID direkt
|
||||
note = self._fetch_note_cached(ref_str)
|
||||
if note:
|
||||
return note
|
||||
if self._fetch_note_cached(ref_str): return self._fetch_note_cached(ref_str)
|
||||
|
||||
# Fall C: Titel (exakte Übereinstimmung)
|
||||
# Fall C: Titel
|
||||
res, _ = self.client.scroll(
|
||||
collection_name=self.notes_col,
|
||||
scroll_filter=models.Filter(must=[
|
||||
models.FieldCondition(key="title", match=models.MatchValue(value=ref_str))
|
||||
]),
|
||||
scroll_filter=models.Filter(must=[models.FieldCondition(key="title", match=models.MatchValue(value=ref_str))]),
|
||||
limit=1, with_payload=True
|
||||
)
|
||||
if res and res[0].payload:
|
||||
payload = res[0].payload
|
||||
self._note_cache[payload['note_id']] = payload
|
||||
return payload
|
||||
|
||||
# Fall D: Titel (Text-Suche für Fuzzy-Matching)
|
||||
res, _ = self.client.scroll(
|
||||
collection_name=self.notes_col,
|
||||
scroll_filter=models.Filter(must=[
|
||||
models.FieldCondition(key="title", match=models.MatchText(text=ref_str))
|
||||
]),
|
||||
limit=1, with_payload=True
|
||||
)
|
||||
if res and res[0].payload:
|
||||
payload = res[0].payload
|
||||
self._note_cache[payload['note_id']] = payload
|
||||
return payload
|
||||
|
||||
if res:
|
||||
self._note_cache[res[0].payload['note_id']] = res[0].payload
|
||||
return res[0].payload
|
||||
return None
|
||||
|
||||
def _add_node_to_dict(self, node_dict, note_payload, level=1):
|
||||
nid = note_payload.get("note_id")
|
||||
if not nid or nid in node_dict: return
|
||||
if nid in node_dict: return
|
||||
|
||||
ntype = note_payload.get("type", "default")
|
||||
color = GRAPH_COLORS.get(ntype, GRAPH_COLORS["default"])
|
||||
|
|
|
|||
124
app/main.py
124
app/main.py
|
|
@ -1,29 +1,25 @@
|
|||
"""
|
||||
FILE: app/main.py
|
||||
DESCRIPTION: Bootstrap der FastAPI Anwendung für WP-25a (Agentic MoE).
|
||||
Orchestriert Lifespan-Events, globale Fehlerbehandlung und Routing.
|
||||
Prüft beim Start die Integrität der Mixture of Experts Konfiguration.
|
||||
VERSION: 1.1.0 (WP-25a: MoE Integrity Check)
|
||||
DESCRIPTION: Bootstrap der FastAPI Anwendung. Inkludiert Router und Middleware.
|
||||
VERSION: 0.6.0
|
||||
STATUS: Active
|
||||
DEPENDENCIES: app.config, app.routers.*, app.services.llm_service
|
||||
DEPENDENCIES: app.config, app.routers.* (embed, qdrant, query, graph, tools, feedback, chat, ingest, admin)
|
||||
LAST_ANALYSIS: 2025-12-15
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from fastapi import FastAPI
|
||||
from .config import get_settings
|
||||
from .services.llm_service import LLMService
|
||||
#from .routers.embed_router import router as embed_router
|
||||
#from .routers.qdrant_router import router as qdrant_router
|
||||
|
||||
# Import der Router
|
||||
from .routers.query import router as query_router
|
||||
from .routers.graph import router as graph_router
|
||||
from .routers.tools import router as tools_router
|
||||
from .routers.feedback import router as feedback_router
|
||||
# NEU: Chat Router (WP-05)
|
||||
from .routers.chat import router as chat_router
|
||||
# NEU: Ingest Router (WP-11)
|
||||
from .routers.ingest import router as ingest_router
|
||||
|
||||
try:
|
||||
|
|
@ -31,109 +27,26 @@ try:
|
|||
except Exception:
|
||||
admin_router = None
|
||||
|
||||
from .core.logging_setup import setup_logging
|
||||
|
||||
# Initialisierung des Loggings noch VOR create_app()
|
||||
setup_logging()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# --- WP-25a: Lifespan Management mit MoE Integritäts-Prüfung ---
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""
|
||||
Verwaltet den Lebenszyklus der Anwendung (Startup/Shutdown).
|
||||
Verifiziert die Verfügbarkeit der MoE-Experten-Profile und Strategien.
|
||||
"""
|
||||
settings = get_settings()
|
||||
logger.info("🚀 mindnet API: Starting up (WP-25a MoE Mode)...")
|
||||
|
||||
# 1. Startup: Integritäts-Check der MoE Konfiguration
|
||||
# Wir prüfen die drei Säulen der Agentic-RAG Architektur.
|
||||
decision_cfg = os.getenv("MINDNET_DECISION_CONFIG", "config/decision_engine.yaml")
|
||||
profiles_cfg = getattr(settings, "LLM_PROFILES_PATH", "config/llm_profiles.yaml")
|
||||
prompts_cfg = settings.PROMPTS_PATH
|
||||
|
||||
missing_files = []
|
||||
if not os.path.exists(decision_cfg): missing_files.append(decision_cfg)
|
||||
if not os.path.exists(profiles_cfg): missing_files.append(profiles_cfg)
|
||||
if not os.path.exists(prompts_cfg): missing_files.append(prompts_cfg)
|
||||
|
||||
if missing_files:
|
||||
logger.error(f"❌ CRITICAL: Missing MoE config files: {missing_files}")
|
||||
else:
|
||||
logger.info("✅ MoE Configuration files verified.")
|
||||
|
||||
yield
|
||||
|
||||
# 2. Shutdown: Ressourcen bereinigen
|
||||
logger.info("🛑 mindnet API: Shutting down...")
|
||||
try:
|
||||
llm = LLMService()
|
||||
await llm.close()
|
||||
logger.info("✨ LLM resources cleaned up.")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Error during LLMService cleanup: {e}")
|
||||
|
||||
logger.info("Goodbye.")
|
||||
|
||||
# --- App Factory ---
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""Initialisiert die FastAPI App mit WP-25a Erweiterungen."""
|
||||
app = FastAPI(
|
||||
title="mindnet API",
|
||||
version="1.1.0", # WP-25a Milestone
|
||||
lifespan=lifespan,
|
||||
description="Digital Twin Knowledge Engine mit Mixture of Experts Orchestration."
|
||||
)
|
||||
|
||||
app = FastAPI(title="mindnet API", version="0.6.0") # Version bump WP-11
|
||||
s = get_settings()
|
||||
|
||||
# --- Globale Fehlerbehandlung (WP-25a Resilienz) ---
|
||||
|
||||
@app.exception_handler(Exception)
|
||||
async def global_exception_handler(request: Request, exc: Exception):
|
||||
"""Fängt unerwartete Fehler in der MoE-Prozesskette ab."""
|
||||
logger.error(f"❌ Unhandled Engine Error: {exc}", exc_info=True)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={
|
||||
"detail": "Ein interner Fehler ist in der MoE-Kette aufgetreten.",
|
||||
"error_type": type(exc).__name__
|
||||
}
|
||||
)
|
||||
|
||||
# Healthcheck
|
||||
@app.get("/healthz")
|
||||
def healthz():
|
||||
"""Bietet Statusinformationen über die Engine und Datenbank-Verbindung."""
|
||||
# WP-24c v4.5.10: Prüfe EdgeDTO-Version zur Laufzeit
|
||||
edge_dto_supports_callout = False
|
||||
try:
|
||||
from app.models.dto import EdgeDTO
|
||||
import inspect
|
||||
source = inspect.getsource(EdgeDTO)
|
||||
edge_dto_supports_callout = "explicit:callout" in source
|
||||
except Exception:
|
||||
pass # Fehler beim Prüfen ist nicht kritisch
|
||||
return {"status": "ok", "qdrant": s.QDRANT_URL, "prefix": s.COLLECTION_PREFIX}
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"version": "1.1.0",
|
||||
"qdrant": s.QDRANT_URL,
|
||||
"prefix": s.COLLECTION_PREFIX,
|
||||
"moe_enabled": True,
|
||||
"edge_dto_supports_callout": edge_dto_supports_callout # WP-24c v4.5.10: Diagnose-Hilfe
|
||||
}
|
||||
# app.include_router(embed_router)
|
||||
# app.include_router(qdrant_router)
|
||||
|
||||
# Inkludieren der Router (100% Kompatibilität erhalten)
|
||||
app.include_router(query_router, prefix="/query", tags=["query"])
|
||||
app.include_router(graph_router, prefix="/graph", tags=["graph"])
|
||||
app.include_router(tools_router, prefix="/tools", tags=["tools"])
|
||||
app.include_router(feedback_router, prefix="/feedback", tags=["feedback"])
|
||||
app.include_router(chat_router, prefix="/chat", tags=["chat"]) # WP-25a Agentic Chat
|
||||
|
||||
# NEU: Chat Endpoint
|
||||
app.include_router(chat_router, prefix="/chat", tags=["chat"])
|
||||
|
||||
# NEU: Ingest Endpoint
|
||||
app.include_router(ingest_router, prefix="/ingest", tags=["ingest"])
|
||||
|
||||
if admin_router:
|
||||
|
|
@ -141,5 +54,4 @@ def create_app() -> FastAPI:
|
|||
|
||||
return app
|
||||
|
||||
# Instanziierung der App
|
||||
app = create_app()
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
"""
|
||||
FILE: app/models/dto.py
|
||||
DESCRIPTION: Pydantic-Modelle (DTOs) für Request/Response Bodies. Definiert das API-Schema.
|
||||
VERSION: 0.7.1 (WP-25: Stream-Tracing Support)
|
||||
VERSION: 0.6.6 (WP-22 Debug & Stability Update)
|
||||
STATUS: Active
|
||||
DEPENDENCIES: pydantic, typing, uuid
|
||||
LAST_ANALYSIS: 2025-12-18
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -11,14 +12,8 @@ from pydantic import BaseModel, Field
|
|||
from typing import List, Literal, Optional, Dict, Any
|
||||
import uuid
|
||||
|
||||
# WP-25: Erweiterte Kanten-Typen gemäß neuer decision_engine.yaml
|
||||
EdgeKind = Literal[
|
||||
"references", "references_at", "backlink", "next", "prev",
|
||||
"belongs_to", "depends_on", "related_to", "similar_to",
|
||||
"caused_by", "derived_from", "based_on", "solves", "blocks",
|
||||
"uses", "guides", "enforced_by", "implemented_in", "part_of",
|
||||
"experienced_in", "impacts", "risk_of"
|
||||
]
|
||||
# Gültige Kanten-Typen gemäß Manual
|
||||
EdgeKind = Literal["references", "references_at", "backlink", "next", "prev", "belongs_to", "depends_on", "related_to", "similar_to", "caused_by", "derived_from", "based_on", "solves", "blocks", "uses", "guides"]
|
||||
|
||||
|
||||
# --- Basis-DTOs ---
|
||||
|
|
@ -46,24 +41,15 @@ class EdgeDTO(BaseModel):
|
|||
target: str
|
||||
weight: float
|
||||
direction: Literal["out", "in", "undirected"] = "out"
|
||||
# WP-24c v4.5.3: Erweiterte Provenance-Werte für Chunk-Aware Edges
|
||||
# Unterstützt alle tatsächlich verwendeten Provenance-Typen im System
|
||||
provenance: Optional[Literal[
|
||||
"explicit", "rule", "smart", "structure",
|
||||
"explicit:callout", "explicit:wikilink", "explicit:note_zone", "explicit:note_scope",
|
||||
"inline:rel", "callout:edge", "semantic_ai", "structure:belongs_to", "structure:order",
|
||||
"derived:backlink", "edge_defaults", "global_pool"
|
||||
]] = "explicit"
|
||||
provenance: Optional[Literal["explicit", "rule", "smart", "structure"]] = "explicit"
|
||||
confidence: float = 1.0
|
||||
target_section: Optional[str] = None
|
||||
|
||||
|
||||
# --- Request Models ---
|
||||
|
||||
class QueryRequest(BaseModel):
|
||||
"""
|
||||
Request für /query. Unterstützt Multi-Stream Isolation via filters.
|
||||
WP-24c v4.1.0: Erweitert um Section-Filtering und Scope-Awareness.
|
||||
Request für /query.
|
||||
"""
|
||||
mode: Literal["semantic", "edge", "hybrid"] = "hybrid"
|
||||
query: Optional[str] = None
|
||||
|
|
@ -74,15 +60,14 @@ class QueryRequest(BaseModel):
|
|||
ret: Dict = {"with_paths": True, "with_notes": True, "with_chunks": True}
|
||||
explain: bool = False
|
||||
|
||||
# WP-22/25: Dynamische Gewichtung der Graphen-Highways
|
||||
# WP-22: Semantic Graph Routing
|
||||
boost_edges: Optional[Dict[str, float]] = None
|
||||
|
||||
# WP-24c v4.1.0: Section-Filtering für präzise Section-Links
|
||||
target_section: Optional[str] = None
|
||||
|
||||
|
||||
class FeedbackRequest(BaseModel):
|
||||
"""User-Feedback zu einem spezifischen Treffer oder der Gesamtantwort."""
|
||||
"""
|
||||
User-Feedback zu einem spezifischen Treffer oder der Gesamtantwort (WP-08 Basis).
|
||||
"""
|
||||
query_id: str = Field(..., description="ID der ursprünglichen Suche")
|
||||
node_id: str = Field(..., description="ID des bewerteten Treffers oder 'generated_answer'")
|
||||
score: int = Field(..., ge=1, le=5, description="1 (Irrelevant) bis 5 (Perfekt)")
|
||||
|
|
@ -90,14 +75,16 @@ class FeedbackRequest(BaseModel):
|
|||
|
||||
|
||||
class ChatRequest(BaseModel):
|
||||
"""Request für /chat (WP-25 Einstieg)."""
|
||||
"""
|
||||
WP-05: Request für /chat.
|
||||
"""
|
||||
message: str = Field(..., description="Die Nachricht des Users")
|
||||
conversation_id: Optional[str] = Field(None, description="ID für Chat-Verlauf")
|
||||
top_k: int = 5
|
||||
explain: bool = False
|
||||
|
||||
|
||||
# --- Explanation Models ---
|
||||
# --- WP-04b Explanation Models ---
|
||||
|
||||
class ScoreBreakdown(BaseModel):
|
||||
"""Aufschlüsselung der Score-Komponenten nach der WP-22 Formel."""
|
||||
|
|
@ -108,14 +95,14 @@ class ScoreBreakdown(BaseModel):
|
|||
raw_edge_bonus: float
|
||||
raw_centrality: float
|
||||
node_weight: float
|
||||
# WP-22 Debug Fields für Messbarkeit
|
||||
status_multiplier: float = 1.0
|
||||
graph_boost_factor: float = 1.0
|
||||
|
||||
|
||||
class Reason(BaseModel):
|
||||
"""Ein semantischer Grund für das Ranking."""
|
||||
# WP-25: 'status' hinzugefügt für Synchronität mit retriever.py
|
||||
kind: Literal["semantic", "edge", "type", "centrality", "lifecycle", "status"]
|
||||
kind: Literal["semantic", "edge", "type", "centrality", "lifecycle"]
|
||||
message: str
|
||||
score_impact: Optional[float] = None
|
||||
details: Optional[Dict[str, Any]] = None
|
||||
|
|
@ -126,6 +113,7 @@ class Explanation(BaseModel):
|
|||
breakdown: ScoreBreakdown
|
||||
reasons: List[Reason]
|
||||
related_edges: Optional[List[EdgeDTO]] = None
|
||||
# WP-22 Debug: Verifizierung des Routings
|
||||
applied_intent: Optional[str] = None
|
||||
applied_boosts: Optional[Dict[str, float]] = None
|
||||
|
||||
|
|
@ -133,11 +121,7 @@ class Explanation(BaseModel):
|
|||
# --- Response Models ---
|
||||
|
||||
class QueryHit(BaseModel):
|
||||
"""
|
||||
Einzelnes Trefferobjekt.
|
||||
WP-25: stream_origin hinzugefügt für Tracing und Feedback-Optimierung.
|
||||
WP-24c v4.1.0: source_chunk_id für RAG-Kontext hinzugefügt.
|
||||
"""
|
||||
"""Einzelnes Trefferobjekt für /query."""
|
||||
node_id: str
|
||||
note_id: str
|
||||
semantic_score: float
|
||||
|
|
@ -148,12 +132,10 @@ class QueryHit(BaseModel):
|
|||
source: Optional[Dict] = None
|
||||
payload: Optional[Dict] = None
|
||||
explanation: Optional[Explanation] = None
|
||||
stream_origin: Optional[str] = Field(None, description="Name des Ursprungs-Streams")
|
||||
source_chunk_id: Optional[str] = Field(None, description="Chunk-ID der Quelle (für RAG-Kontext)")
|
||||
|
||||
|
||||
class QueryResponse(BaseModel):
|
||||
"""Antwortstruktur für /query (wird von DecisionEngine Streams genutzt)."""
|
||||
"""Antwortstruktur für /query."""
|
||||
query_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
||||
results: List[QueryHit]
|
||||
used_mode: str
|
||||
|
|
@ -170,12 +152,11 @@ class GraphResponse(BaseModel):
|
|||
|
||||
class ChatResponse(BaseModel):
|
||||
"""
|
||||
Antwortstruktur für /chat.
|
||||
WP-25: 'intent' spiegelt nun die gewählte Strategie wider.
|
||||
WP-05/06: Antwortstruktur für /chat.
|
||||
"""
|
||||
query_id: str = Field(..., description="Traceability ID")
|
||||
answer: str = Field(..., description="Generierte Antwort vom LLM")
|
||||
sources: List[QueryHit] = Field(..., description="Die genutzten Quellen (alle Streams)")
|
||||
sources: List[QueryHit] = Field(..., description="Die genutzten Quellen")
|
||||
latency_ms: int
|
||||
intent: Optional[str] = Field("FACT", description="Die gewählte WP-25 Strategie")
|
||||
intent_source: Optional[str] = Field("LLM_Router", description="Quelle der Intent-Erkennung")
|
||||
intent: Optional[str] = Field("FACT", description="WP-06: Erkannter Intent")
|
||||
intent_source: Optional[str] = Field("Unknown", description="Quelle der Intent-Erkennung")
|
||||
|
|
@ -1,77 +1,64 @@
|
|||
"""
|
||||
FILE: app/routers/chat.py
|
||||
DESCRIPTION: Haupt-Chat-Interface (WP-25b Edition).
|
||||
Kombiniert die spezialisierte Interview-Logik mit der neuen
|
||||
Lazy-Prompt-Orchestration und MoE-Synthese.
|
||||
WP-24c: Integration der Discovery API für proaktive Vernetzung.
|
||||
VERSION: 3.1.0 (WP-24c: Discovery API Integration)
|
||||
DESCRIPTION: Haupt-Chat-Interface (RAG & Interview). Enthält Intent-Router (Keywords/LLM) und Prompt-Construction.
|
||||
VERSION: 2.7.8 (Full Unabridged Stability Edition)
|
||||
STATUS: Active
|
||||
FIX:
|
||||
- WP-24c: Neuer Endpunkt /query/discover für proaktive Kanten-Vorschläge.
|
||||
- WP-25b: Umstellung des Interview-Modus auf Lazy-Prompt (prompt_key + variables).
|
||||
- WP-25b: Delegation der RAG-Phase an die Engine v1.3.0 für konsistente MoE-Steuerung.
|
||||
- WP-25a: Voller Erhalt der v3.0.2 Logik (Interview, Schema-Resolution, FastPaths).
|
||||
1. Implementiert Context-Throttling für Ollama (MAX_OLLAMA_CHARS).
|
||||
2. Deaktiviert LLM-Retries für den Chat (max_retries=0).
|
||||
3. Behebt Double-Fallback-Schleifen und Silent Refusals.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from typing import List, Dict, Any, Optional
|
||||
from pydantic import BaseModel
|
||||
import time
|
||||
import uuid
|
||||
import logging
|
||||
import yaml
|
||||
import os
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from app.config import get_settings
|
||||
from app.models.dto import ChatRequest, ChatResponse, QueryHit, QueryRequest
|
||||
from app.models.dto import ChatRequest, ChatResponse, QueryRequest, QueryHit
|
||||
from app.services.llm_service import LLMService
|
||||
from app.core.retriever import Retriever
|
||||
from app.services.feedback_service import log_search
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# --- EBENE 0: DTOs FÜR DISCOVERY (WP-24c) ---
|
||||
|
||||
class DiscoveryRequest(BaseModel):
|
||||
content: str
|
||||
top_k: int = 8
|
||||
min_confidence: float = 0.6
|
||||
|
||||
class DiscoveryHit(BaseModel):
|
||||
target_note: str # Note ID
|
||||
target_title: str # Menschenlesbarer Titel
|
||||
suggested_edge_type: str # Kanonischer Typ aus edge_vocabulary
|
||||
confidence_score: float # Kombinierter Vektor- + KI-Score
|
||||
reasoning: str # Kurze Begründung der KI
|
||||
|
||||
# --- EBENE 1: CONFIG LOADER & CACHING (WP-25 Standard) ---
|
||||
# --- Helper: Config Loader ---
|
||||
|
||||
_DECISION_CONFIG_CACHE = None
|
||||
_TYPES_CONFIG_CACHE = None
|
||||
|
||||
def _load_decision_config() -> Dict[str, Any]:
|
||||
"""Lädt die Strategie-Konfiguration."""
|
||||
settings = get_settings()
|
||||
path = Path(settings.DECISION_CONFIG_PATH)
|
||||
default_config = {
|
||||
"strategies": {
|
||||
"FACT": {"trigger_keywords": [], "preferred_provider": "openrouter"}
|
||||
}
|
||||
}
|
||||
|
||||
if not path.exists():
|
||||
logger.warning(f"Decision config not found at {path}, using defaults.")
|
||||
return default_config
|
||||
|
||||
try:
|
||||
if path.exists():
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
return yaml.safe_load(f)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load decision config: {e}")
|
||||
return {"strategies": {}}
|
||||
return default_config
|
||||
|
||||
def _load_types_config() -> Dict[str, Any]:
|
||||
"""Lädt die types.yaml für die Typerkennung."""
|
||||
"""Lädt die types.yaml für Keyword-Erkennung."""
|
||||
path = os.getenv("MINDNET_TYPES_FILE", "config/types.yaml")
|
||||
try:
|
||||
if os.path.exists(path):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load types config: {e}")
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def get_full_config() -> Dict[str, Any]:
|
||||
|
|
@ -89,17 +76,21 @@ def get_types_config() -> Dict[str, Any]:
|
|||
def get_decision_strategy(intent: str) -> Dict[str, Any]:
|
||||
config = get_full_config()
|
||||
strategies = config.get("strategies", {})
|
||||
return strategies.get(intent, strategies.get("FACT_WHAT", {}))
|
||||
return strategies.get(intent, strategies.get("FACT", {}))
|
||||
|
||||
# --- EBENE 2: SPEZIAL-LOGIK (INTERVIEW & DETECTION) ---
|
||||
# --- Helper: Target Type Detection (WP-07) ---
|
||||
|
||||
def _detect_target_type(message: str, configured_schemas: Dict[str, Any]) -> str:
|
||||
"""WP-07: Identifiziert den gewünschten Notiz-Typ (Keyword-basiert)."""
|
||||
"""
|
||||
Versucht zu erraten, welchen Notiz-Typ der User erstellen will.
|
||||
Nutzt Keywords aus types.yaml UND Mappings.
|
||||
"""
|
||||
message_lower = message.lower()
|
||||
|
||||
# 1. Check types.yaml detection_keywords (Priority!)
|
||||
types_cfg = get_types_config()
|
||||
types_def = types_cfg.get("types", {})
|
||||
|
||||
# 1. Check types.yaml detection_keywords
|
||||
for type_name, type_data in types_def.items():
|
||||
keywords = type_data.get("detection_keywords", [])
|
||||
for kw in keywords:
|
||||
|
|
@ -112,251 +103,293 @@ def _detect_target_type(message: str, configured_schemas: Dict[str, Any]) -> str
|
|||
if type_key in message_lower:
|
||||
return type_key
|
||||
|
||||
# 3. Synonym-Mapping (Legacy)
|
||||
# 3. Synonym-Mapping (Legacy Fallback)
|
||||
synonyms = {
|
||||
"projekt": "project", "entscheidung": "decision", "ziel": "goal",
|
||||
"erfahrung": "experience", "wert": "value", "prinzip": "principle"
|
||||
"projekt": "project", "vorhaben": "project",
|
||||
"entscheidung": "decision", "beschluss": "decision",
|
||||
"ziel": "goal",
|
||||
"erfahrung": "experience", "lektion": "experience",
|
||||
"wert": "value",
|
||||
"prinzip": "principle",
|
||||
"notiz": "default", "idee": "default"
|
||||
}
|
||||
|
||||
for term, schema_key in synonyms.items():
|
||||
if term in message_lower:
|
||||
return schema_key
|
||||
|
||||
return "default"
|
||||
|
||||
def _is_question(query: str) -> bool:
|
||||
"""Prüft, ob der Input eine Frage ist."""
|
||||
q = query.strip().lower()
|
||||
if "?" in q: return True
|
||||
starters = ["wer", "wie", "was", "wo", "wann", "warum", "weshalb", "wozu", "welche", "bist du"]
|
||||
return any(q.startswith(s + " ") for s in starters)
|
||||
|
||||
async def _classify_intent(query: str, llm: LLMService) -> tuple[str, str]:
|
||||
"""Hybrid Router: Keyword-Fast-Paths & DecisionEngine LLM Router."""
|
||||
config = get_full_config()
|
||||
strategies = config.get("strategies", {})
|
||||
query_lower = query.lower()
|
||||
|
||||
# 1. FAST PATH: Keyword Trigger
|
||||
for intent_name, strategy in strategies.items():
|
||||
keywords = strategy.get("trigger_keywords", [])
|
||||
for k in keywords:
|
||||
if k.lower() in query_lower:
|
||||
return intent_name, "Keyword (FastPath)"
|
||||
|
||||
# 2. FAST PATH B: Type Keywords -> INTERVIEW
|
||||
if not _is_question(query_lower):
|
||||
types_cfg = get_types_config()
|
||||
for type_name, type_data in types_cfg.get("types", {}).items():
|
||||
for kw in type_data.get("detection_keywords", []):
|
||||
if kw.lower() in query_lower:
|
||||
return "INTERVIEW", "Keyword (Interview)"
|
||||
|
||||
# 3. SLOW PATH: DecisionEngine LLM Router (MoE-gesteuert)
|
||||
intent = await llm.decision_engine._determine_strategy(query)
|
||||
return intent, "DecisionEngine (LLM)"
|
||||
|
||||
# --- EBENE 3: RETRIEVAL AGGREGATION ---
|
||||
|
||||
def _collect_all_hits(stream_responses: Dict[str, Any]) -> List[QueryHit]:
|
||||
"""Sammelt deduplizierte Treffer aus allen Streams für das Tracing."""
|
||||
all_hits = []
|
||||
seen_node_ids = set()
|
||||
for _, response in stream_responses.items():
|
||||
# Sammeln der Hits aus den QueryResponse Objekten
|
||||
if hasattr(response, 'results'):
|
||||
for hit in response.results:
|
||||
if hit.node_id not in seen_node_ids:
|
||||
all_hits.append(hit)
|
||||
seen_node_ids.add(hit.node_id)
|
||||
return sorted(all_hits, key=lambda h: h.total_score, reverse=True)
|
||||
|
||||
# --- EBENE 4: ENDPUNKTE ---
|
||||
# --- Dependencies ---
|
||||
|
||||
def get_llm_service():
|
||||
return LLMService()
|
||||
|
||||
def get_retriever():
|
||||
return Retriever()
|
||||
|
||||
|
||||
# --- Logic ---
|
||||
|
||||
def _build_enriched_context(hits: List[QueryHit]) -> str:
|
||||
context_parts = []
|
||||
for i, hit in enumerate(hits, 1):
|
||||
source = hit.source or {}
|
||||
content = (
|
||||
source.get("text") or source.get("content") or
|
||||
source.get("page_content") or source.get("chunk_text") or
|
||||
"[Kein Text]"
|
||||
)
|
||||
title = hit.note_id or "Unbekannt"
|
||||
|
||||
payload = hit.payload or {}
|
||||
note_type = payload.get("type") or source.get("type", "unknown")
|
||||
note_type = str(note_type).upper()
|
||||
|
||||
entry = (
|
||||
f"### QUELLE {i}: {title}\n"
|
||||
f"TYP: [{note_type}] (Score: {hit.total_score:.2f})\n"
|
||||
f"INHALT:\n{content}\n"
|
||||
)
|
||||
context_parts.append(entry)
|
||||
|
||||
return "\n\n".join(context_parts)
|
||||
|
||||
def _is_question(query: str) -> bool:
|
||||
"""Prüft, ob der Input wahrscheinlich eine Frage ist."""
|
||||
q = query.strip().lower()
|
||||
if "?" in q: return True
|
||||
|
||||
# W-Fragen Indikatoren
|
||||
starters = ["wer", "wie", "was", "wo", "wann", "warum", "weshalb", "wozu", "welche", "bist du", "entspricht"]
|
||||
if any(q.startswith(s + " ") for s in starters):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def _classify_intent(query: str, llm: LLMService) -> tuple[str, str]:
|
||||
"""
|
||||
Hybrid Router v5:
|
||||
1. Decision Keywords (Strategie) -> Prio 1
|
||||
2. Type Keywords (Interview Trigger) -> Prio 2
|
||||
3. LLM (Fallback) -> Prio 3
|
||||
"""
|
||||
config = get_full_config()
|
||||
strategies = config.get("strategies", {})
|
||||
settings = config.get("settings", {})
|
||||
|
||||
query_lower = query.lower()
|
||||
|
||||
# 1. FAST PATH A: Strategie Keywords
|
||||
for intent_name, strategy in strategies.items():
|
||||
if intent_name == "FACT": continue
|
||||
keywords = strategy.get("trigger_keywords", [])
|
||||
for k in keywords:
|
||||
if k.lower() in query_lower:
|
||||
return intent_name, "Keyword (Strategy)"
|
||||
|
||||
# 2. FAST PATH B: Type Keywords -> INTERVIEW
|
||||
if not _is_question(query_lower):
|
||||
types_cfg = get_types_config()
|
||||
types_def = types_cfg.get("types", {})
|
||||
|
||||
for type_name, type_data in types_def.items():
|
||||
keywords = type_data.get("detection_keywords", [])
|
||||
for kw in keywords:
|
||||
if kw.lower() in query_lower:
|
||||
return "INTERVIEW", f"Keyword (Type: {type_name})"
|
||||
|
||||
# 3. SLOW PATH: LLM Router
|
||||
if settings.get("llm_fallback_enabled", False):
|
||||
router_prompt_template = llm.get_prompt("llm_router_prompt")
|
||||
|
||||
if router_prompt_template:
|
||||
prompt = router_prompt_template.replace("{query}", query)
|
||||
logger.info("Keywords failed (or Question detected). Asking LLM for Intent...")
|
||||
|
||||
try:
|
||||
# FIX: Auch beim Routing keine Retries im Chat-Fluss
|
||||
raw_response = await llm.generate_raw_response(prompt, priority="realtime", max_retries=0)
|
||||
llm_output_upper = raw_response.upper()
|
||||
|
||||
if "INTERVIEW" in llm_output_upper or "CREATE" in llm_output_upper:
|
||||
return "INTERVIEW", "LLM Router"
|
||||
|
||||
for strat_key in strategies.keys():
|
||||
if strat_key in llm_output_upper:
|
||||
return strat_key, "LLM Router"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Router LLM failed: {e}")
|
||||
|
||||
return "FACT", "Default (No Match)"
|
||||
|
||||
@router.post("/", response_model=ChatResponse)
|
||||
async def chat_endpoint(
|
||||
request: ChatRequest,
|
||||
llm: LLMService = Depends(get_llm_service)
|
||||
llm: LLMService = Depends(get_llm_service),
|
||||
retriever: Retriever = Depends(get_retriever)
|
||||
):
|
||||
start_time = time.time()
|
||||
query_id = str(uuid.uuid4())
|
||||
logger.info(f"🚀 [WP-25b] Chat request [{query_id}]: {request.message[:50]}...")
|
||||
logger.info(f"Chat request [{query_id}]: {request.message[:50]}...")
|
||||
|
||||
try:
|
||||
# 1. Intent Detection
|
||||
intent, intent_source = await _classify_intent(request.message, llm)
|
||||
logger.info(f"[{query_id}] Intent: {intent} via {intent_source}")
|
||||
logger.info(f"[{query_id}] Final Intent: {intent} via {intent_source}")
|
||||
|
||||
# Strategy Load
|
||||
strategy = get_decision_strategy(intent)
|
||||
engine = llm.decision_engine
|
||||
prompt_key = strategy.get("prompt_template", "rag_template")
|
||||
preferred_provider = strategy.get("preferred_provider")
|
||||
|
||||
sources_hits = []
|
||||
answer_text = ""
|
||||
final_prompt = ""
|
||||
context_str = ""
|
||||
|
||||
# 2. INTERVIEW MODE (WP-25b Lazy-Prompt Logik)
|
||||
if intent == "INTERVIEW":
|
||||
# --- INTERVIEW MODE ---
|
||||
target_type = _detect_target_type(request.message, strategy.get("schemas", {}))
|
||||
|
||||
types_cfg = get_types_config()
|
||||
type_def = types_cfg.get("types", {}).get(target_type, {})
|
||||
fields_list = type_def.get("schema", [])
|
||||
|
||||
# WP-07: Restaurierte Fallback Logik
|
||||
if not fields_list:
|
||||
configured_schemas = strategy.get("schemas", {})
|
||||
fallback = configured_schemas.get(target_type, configured_schemas.get("default", {}))
|
||||
fields_list = fallback.get("fields", []) if isinstance(fallback, dict) else (fallback or [])
|
||||
fallback_schema = configured_schemas.get(target_type, configured_schemas.get("default"))
|
||||
if isinstance(fallback_schema, dict):
|
||||
fields_list = fallback_schema.get("fields", [])
|
||||
else:
|
||||
fields_list = fallback_schema or []
|
||||
|
||||
logger.info(f"[{query_id}] Interview Type: {target_type}. Fields: {len(fields_list)}")
|
||||
fields_str = "\n- " + "\n- ".join(fields_list)
|
||||
template_key = strategy.get("prompt_template", "interview_template")
|
||||
|
||||
# WP-25b: Lazy Loading Call
|
||||
answer_text = await llm.generate_raw_response(
|
||||
prompt_key=template_key,
|
||||
variables={
|
||||
"query": request.message,
|
||||
"target_type": target_type,
|
||||
"schema_fields": fields_str
|
||||
},
|
||||
system=llm.get_prompt("system_prompt"),
|
||||
priority="realtime",
|
||||
profile_name="compression_fast",
|
||||
max_retries=0
|
||||
)
|
||||
template = llm.get_prompt(prompt_key)
|
||||
final_prompt = template.replace("{context_str}", "Dialogverlauf...") \
|
||||
.replace("{query}", request.message) \
|
||||
.replace("{target_type}", target_type) \
|
||||
.replace("{schema_fields}", fields_str) \
|
||||
.replace("{schema_hint}", "")
|
||||
sources_hits = []
|
||||
|
||||
# 3. RAG MODE (WP-25b Delegation an Engine v1.3.0)
|
||||
else:
|
||||
# Phase A & B: Retrieval & Kompression (Delegiert an Engine v1.3.0)
|
||||
formatted_context_map = await engine._execute_parallel_streams(strategy, request.message)
|
||||
# --- RAG MODE (FACT, DECISION, EMPATHY, CODING) ---
|
||||
inject_types = strategy.get("inject_types", [])
|
||||
prepend_instr = strategy.get("prepend_instruction", "")
|
||||
edge_boosts = strategy.get("edge_boosts", {})
|
||||
|
||||
# Erfassung der Quellen für das Tracing
|
||||
raw_stream_map = {}
|
||||
stream_keys = strategy.get("use_streams", [])
|
||||
library = engine.config.get("streams_library", {})
|
||||
|
||||
retrieval_tasks = []
|
||||
active_streams = []
|
||||
for key in stream_keys:
|
||||
if key in library:
|
||||
active_streams.append(key)
|
||||
retrieval_tasks.append(engine._run_single_stream(key, library[key], request.message))
|
||||
|
||||
responses = await asyncio.gather(*retrieval_tasks, return_exceptions=True)
|
||||
for name, res in zip(active_streams, responses):
|
||||
if not isinstance(res, Exception):
|
||||
raw_stream_map[name] = res
|
||||
|
||||
sources_hits = _collect_all_hits(raw_stream_map)
|
||||
|
||||
# Phase C: Finale MoE Synthese (Delegiert an Engine v1.3.0)
|
||||
answer_text = await engine._generate_final_answer(
|
||||
intent, strategy, request.message, formatted_context_map
|
||||
query_req = QueryRequest(
|
||||
query=request.message,
|
||||
mode="hybrid",
|
||||
top_k=request.top_k,
|
||||
explain=request.explain,
|
||||
boost_edges=edge_boosts
|
||||
)
|
||||
retrieve_result = await retriever.search(query_req)
|
||||
hits = retrieve_result.results
|
||||
|
||||
if inject_types:
|
||||
strategy_req = QueryRequest(
|
||||
query=request.message,
|
||||
mode="hybrid",
|
||||
top_k=3,
|
||||
filters={"type": inject_types},
|
||||
explain=False,
|
||||
boost_edges=edge_boosts
|
||||
)
|
||||
strategy_result = await retriever.search(strategy_req)
|
||||
existing_ids = {h.node_id for h in hits}
|
||||
for strat_hit in strategy_result.results:
|
||||
if strat_hit.node_id not in existing_ids:
|
||||
hits.append(strat_hit)
|
||||
|
||||
context_str = _build_enriched_context(hits) if hits else "Keine relevanten Notizen gefunden."
|
||||
|
||||
# --- STABILITY FIX: OLLAMA CONTEXT THROTTLE ---
|
||||
# Begrenzt den Text, um den "decode: cannot decode batches" Fehler zu vermeiden.
|
||||
# MAX_OLLAMA_CHARS = 10000
|
||||
|
||||
settings = get_settings() # Falls noch nicht im Scope vorhanden
|
||||
max_chars = getattr(settings, "MAX_OLLAMA_CHARS", 10000)
|
||||
if preferred_provider == "ollama" and len(context_str) > max_chars:
|
||||
logger.warning(f"⚠️ [{query_id}] Context zu groß für Ollama ({len(context_str)} chars). Kürze auf {max_chars}.")
|
||||
context_str = context_str[:max_chars] + "\n[...gekürzt zur Stabilität...]"
|
||||
|
||||
template = llm.get_prompt(prompt_key) or "{context_str}\n\n{query}"
|
||||
|
||||
if prepend_instr:
|
||||
context_str = f"{prepend_instr}\n\n{context_str}"
|
||||
|
||||
final_prompt = template.replace("{context_str}", context_str).replace("{query}", request.message)
|
||||
sources_hits = hits
|
||||
|
||||
# --- DEBUG SPOT 1: PROMPT CONSTRUCTION ---
|
||||
logger.info(f"[{query_id}] PROMPT CONSTRUCTION COMPLETE. Length: {len(final_prompt)} chars.")
|
||||
if not final_prompt.strip():
|
||||
logger.error(f"[{query_id}] CRITICAL: Final prompt is empty before sending to LLM!")
|
||||
|
||||
# --- GENERATION WITH NO-RETRY & DEEP FALLBACK ---
|
||||
system_prompt = llm.get_prompt("system_prompt")
|
||||
|
||||
# --- DEBUG SPOT 2: PRIMARY CALL ---
|
||||
logger.info(f"[{query_id}] PRIMARY CALL: Sending request to provider '{preferred_provider}' (No Retries)...")
|
||||
|
||||
answer_text = ""
|
||||
try:
|
||||
# FIX: max_retries=0 verhindert Hänger durch Retry-Kaskaden im Chat
|
||||
answer_text = await llm.generate_raw_response(
|
||||
prompt=final_prompt,
|
||||
system=system_prompt,
|
||||
priority="realtime",
|
||||
provider=preferred_provider,
|
||||
max_retries=0
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"🛑 [{query_id}] Primary Provider '{preferred_provider}' failed: {e}")
|
||||
|
||||
# DEEP FALLBACK: Wenn die Antwort leer ist (Silent Refusal) oder der Primary abgestürzt ist
|
||||
if not answer_text.strip() and preferred_provider != "ollama":
|
||||
# --- DEBUG SPOT 3: FALLBACK TRIGGER ---
|
||||
logger.warning(f"🛑 [{query_id}] PRIMARY '{preferred_provider}' returned EMPTY or FAILED. Triggering Deep Fallback to Ollama...")
|
||||
|
||||
try:
|
||||
answer_text = await llm.generate_raw_response(
|
||||
prompt=final_prompt,
|
||||
system=system_prompt,
|
||||
priority="realtime",
|
||||
provider="ollama",
|
||||
max_retries=0
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"🛑 [{query_id}] Deep Fallback to Ollama also failed: {e}")
|
||||
answer_text = "Entschuldigung, das System ist aktuell überlastet. Bitte versuche es in einem Moment erneut."
|
||||
|
||||
duration_ms = int((time.time() - start_time) * 1000)
|
||||
|
||||
# Logging (WP-15)
|
||||
# Logging
|
||||
try:
|
||||
log_search(
|
||||
query_id=query_id, query_text=request.message, results=sources_hits,
|
||||
mode=f"wp25b_{intent.lower()}", metadata={"strategy": intent, "source": intent_source}
|
||||
query_id=query_id,
|
||||
query_text=request.message,
|
||||
results=sources_hits,
|
||||
mode="interview" if intent == "INTERVIEW" else "chat_rag",
|
||||
metadata={"intent": intent, "source": intent_source, "provider": preferred_provider}
|
||||
)
|
||||
except: pass
|
||||
|
||||
return ChatResponse(
|
||||
query_id=query_id, answer=answer_text, sources=sources_hits,
|
||||
latency_ms=duration_ms, intent=intent, intent_source=intent_source
|
||||
query_id=query_id,
|
||||
answer=answer_text,
|
||||
sources=sources_hits,
|
||||
latency_ms=duration_ms,
|
||||
intent=intent,
|
||||
intent_source=intent_source
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Chat Endpoint Failure: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="Fehler bei der Verarbeitung der Anfrage.")
|
||||
|
||||
@router.post("/query/discover", response_model=List[DiscoveryHit])
|
||||
async def discover_edges(
|
||||
request: DiscoveryRequest,
|
||||
llm: LLMService = Depends(get_llm_service)
|
||||
):
|
||||
"""
|
||||
WP-24c: Analysiert Text auf potenzielle Kanten zu bestehendem Wissen.
|
||||
Nutzt Vektor-Suche und DecisionEngine-Logik (WP-25b PROMPT-TRACE konform).
|
||||
"""
|
||||
start_time = time.time()
|
||||
logger.info(f"🔍 [WP-24c] Discovery triggered for content: {request.content[:50]}...")
|
||||
|
||||
try:
|
||||
# 1. Kandidaten-Suche via Retriever (Vektor-Match)
|
||||
search_req = QueryRequest(
|
||||
query=request.content,
|
||||
top_k=request.top_k,
|
||||
explain=True
|
||||
)
|
||||
candidates = await llm.decision_engine.retriever.search(search_req)
|
||||
|
||||
if not candidates.results:
|
||||
logger.info("ℹ️ No candidates found for discovery.")
|
||||
return []
|
||||
|
||||
# 2. KI-gestützte Beziehungs-Extraktion (WP-25b)
|
||||
discovery_results = []
|
||||
|
||||
# Zugriff auf gültige Kanten-Typen aus der Registry
|
||||
from app.services.edge_registry import registry as edge_reg
|
||||
valid_types_str = ", ".join(list(edge_reg.valid_types))
|
||||
|
||||
# Parallele Evaluierung der Kandidaten für maximale Performance
|
||||
async def evaluate_candidate(hit: QueryHit) -> Optional[DiscoveryHit]:
|
||||
if hit.total_score < request.min_confidence:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Nutzt ingest_extractor Profil für präzise semantische Analyse
|
||||
# Wir verwenden das prompt_key Pattern (edge_extraction) gemäß WP-24c Vorgabe
|
||||
raw_suggestion = await llm.generate_raw_response(
|
||||
prompt_key="edge_extraction",
|
||||
variables={
|
||||
"note_id": "NEUER_INHALT",
|
||||
"text": f"PROXIMITY_TARGET: {hit.source.get('text', '')}\n\nNEW_CONTENT: {request.content}",
|
||||
"valid_types": valid_types_str
|
||||
},
|
||||
profile_name="ingest_extractor",
|
||||
priority="realtime"
|
||||
)
|
||||
|
||||
# Parsing der LLM Antwort (Erwartet JSON Liste)
|
||||
from app.core.ingestion.ingestion_utils import extract_json_from_response
|
||||
suggestions = extract_json_from_response(raw_suggestion)
|
||||
|
||||
if isinstance(suggestions, list) and len(suggestions) > 0:
|
||||
sugg = suggestions[0] # Wir nehmen den stärksten Vorschlag pro Hit
|
||||
return DiscoveryHit(
|
||||
target_note=hit.note_id,
|
||||
target_title=hit.source.get("title") or hit.note_id,
|
||||
suggested_edge_type=sugg.get("kind", "related_to"),
|
||||
confidence_score=hit.total_score,
|
||||
reasoning=f"Semantische Nähe ({int(hit.total_score*100)}%) entdeckt."
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Discovery evaluation failed for hit {hit.note_id}: {e}")
|
||||
return None
|
||||
|
||||
tasks = [evaluate_candidate(hit) for hit in candidates.results]
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
# Zusammenführung und Duplikat-Bereinigung
|
||||
seen_targets = set()
|
||||
for r in results:
|
||||
if r and r.target_note not in seen_targets:
|
||||
discovery_results.append(r)
|
||||
seen_targets.add(r.target_note)
|
||||
|
||||
duration = int((time.time() - start_time) * 1000)
|
||||
logger.info(f"✨ Discovery finished: found {len(discovery_results)} edges in {duration}ms")
|
||||
|
||||
return discovery_results
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Discovery API failure: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="Discovery-Prozess fehlgeschlagen.")
|
||||
logger.error(f"Error in chat endpoint: {e}", exc_info=True)
|
||||
# Wir geben eine benutzerfreundliche Meldung zurück, statt nur den Error-Stack
|
||||
raise HTTPException(status_code=500, detail="Das System konnte die Anfrage nicht verarbeiten.")
|
||||
|
|
@ -12,7 +12,7 @@ from typing import List, Optional
|
|||
from fastapi import APIRouter, Query
|
||||
from qdrant_client import QdrantClient
|
||||
from app.models.dto import GraphResponse, NodeDTO, EdgeDTO
|
||||
from app.core.graph.graph_subgraph import expand
|
||||
from app.core.graph_adapter import expand
|
||||
from app.config import get_settings
|
||||
|
||||
router = APIRouter()
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ LAST_ANALYSIS: 2025-12-15
|
|||
from __future__ import annotations
|
||||
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
||||
from app.models.dto import QueryRequest, QueryResponse
|
||||
from app.core.retrieval.retriever import hybrid_retrieve, semantic_retrieve
|
||||
from app.core.retriever import hybrid_retrieve, semantic_retrieve
|
||||
# NEU:
|
||||
from app.services.feedback_service import log_search
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
"""
|
||||
FILE: app/services/discovery.py
|
||||
DESCRIPTION: Service für WP-11 (Discovery API). Analysiert Entwürfe, findet Entitäten
|
||||
und schlägt typisierte Verbindungen basierend auf der Topologie vor.
|
||||
WP-24c: Vollständige Umstellung auf EdgeRegistry für dynamische Vorschläge.
|
||||
WP-15b: Unterstützung für hybride Suche und Alias-Erkennung.
|
||||
VERSION: 1.1.0 (WP-24c: Full Registry Integration & Audit Fix)
|
||||
DESCRIPTION: Service für WP-11. Analysiert Texte, findet Entitäten und schlägt typisierte Verbindungen vor ("Matrix-Logic").
|
||||
VERSION: 0.6.0
|
||||
STATUS: Active
|
||||
COMPATIBILITY: 100% (Identische API-Signatur wie v0.6.0)
|
||||
DEPENDENCIES: app.core.qdrant, app.models.dto, app.core.retriever
|
||||
EXTERNAL_CONFIG: config/types.yaml
|
||||
LAST_ANALYSIS: 2025-12-15
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
|
|
@ -14,184 +13,207 @@ import os
|
|||
from typing import List, Dict, Any, Optional, Set
|
||||
import yaml
|
||||
|
||||
from app.core.database.qdrant import QdrantConfig, get_client
|
||||
from app.core.qdrant import QdrantConfig, get_client
|
||||
from app.models.dto import QueryRequest
|
||||
from app.core.retrieval.retriever import hybrid_retrieve
|
||||
# WP-24c: Zentrale Topologie-Quelle
|
||||
from app.services.edge_registry import registry as edge_registry
|
||||
from app.core.retriever import hybrid_retrieve
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DiscoveryService:
|
||||
def __init__(self, collection_prefix: str = None):
|
||||
"""Initialisiert den Discovery Service mit Qdrant-Anbindung."""
|
||||
self.cfg = QdrantConfig.from_env()
|
||||
self.prefix = collection_prefix or self.cfg.prefix or "mindnet"
|
||||
self.client = get_client(self.cfg)
|
||||
|
||||
# Die Registry wird für Typ-Metadaten geladen (Schema-Validierung)
|
||||
self.registry = self._load_type_registry()
|
||||
|
||||
async def analyze_draft(self, text: str, current_type: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Analysiert einen Textentwurf auf potenzielle Verbindungen.
|
||||
1. Findet exakte Treffer (Titel/Aliasse).
|
||||
2. Führt semantische Suchen für verschiedene Textabschnitte aus.
|
||||
3. Schlägt topologisch korrekte Kanten-Typen vor.
|
||||
Analysiert den Text und liefert Vorschläge mit kontext-sensitiven Kanten-Typen.
|
||||
"""
|
||||
if not text or len(text.strip()) < 3:
|
||||
return {"suggestions": [], "status": "empty_input"}
|
||||
|
||||
suggestions = []
|
||||
seen_target_ids = set()
|
||||
|
||||
# --- PHASE 1: EXACT MATCHES (TITEL & ALIASSE) ---
|
||||
# Lädt alle bekannten Titel/Aliasse für einen schnellen Scan
|
||||
# Fallback, falls keine spezielle Regel greift
|
||||
default_edge_type = self._get_default_edge_type(current_type)
|
||||
|
||||
# Tracking-Sets für Deduplizierung (Wir merken uns NOTE-IDs)
|
||||
seen_target_note_ids = set()
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 1. Exact Match: Titel/Aliases
|
||||
# ---------------------------------------------------------
|
||||
# Holt Titel, Aliases UND Typen aus dem Index
|
||||
known_entities = self._fetch_all_titles_and_aliases()
|
||||
exact_matches = self._find_entities_in_text(text, known_entities)
|
||||
found_entities = self._find_entities_in_text(text, known_entities)
|
||||
|
||||
for entity in exact_matches:
|
||||
target_id = entity["id"]
|
||||
if target_id in seen_target_ids:
|
||||
for entity in found_entities:
|
||||
if entity["id"] in seen_target_note_ids:
|
||||
continue
|
||||
seen_target_note_ids.add(entity["id"])
|
||||
|
||||
seen_target_ids.add(target_id)
|
||||
# INTELLIGENTE KANTEN-LOGIK (MATRIX)
|
||||
target_type = entity.get("type", "concept")
|
||||
|
||||
# WP-24c: Dynamische Kanten-Ermittlung statt Hardcoded Matrix
|
||||
suggested_kind = self._resolve_edge_type(current_type, target_type)
|
||||
smart_edge = self._resolve_edge_type(current_type, target_type)
|
||||
|
||||
suggestions.append({
|
||||
"type": "exact_match",
|
||||
"text_found": entity["match"],
|
||||
"target_title": entity["title"],
|
||||
"target_id": target_id,
|
||||
"suggested_edge_type": suggested_kind,
|
||||
"suggested_markdown": f"[[rel:{suggest_kind} {entity['title']}]]",
|
||||
"target_id": entity["id"],
|
||||
"suggested_edge_type": smart_edge,
|
||||
"suggested_markdown": f"[[rel:{smart_edge} {entity['title']}]]",
|
||||
"confidence": 1.0,
|
||||
"reason": f"Direkte Erwähnung von '{entity['match']}' ({target_type})"
|
||||
"reason": f"Exakter Treffer: '{entity['match']}' ({target_type})"
|
||||
})
|
||||
|
||||
# --- PHASE 2: SEMANTIC MATCHES (VECTOR SEARCH) ---
|
||||
# Erzeugt Suchanfragen für verschiedene Fenster des Textes
|
||||
# ---------------------------------------------------------
|
||||
# 2. Semantic Match: Sliding Window & Footer Focus
|
||||
# ---------------------------------------------------------
|
||||
search_queries = self._generate_search_queries(text)
|
||||
|
||||
# Parallele Ausführung der Suchanfragen (Cloud-Performance)
|
||||
# Async parallel abfragen
|
||||
tasks = [self._get_semantic_suggestions_async(q) for q in search_queries]
|
||||
results_list = await asyncio.gather(*tasks)
|
||||
|
||||
# Ergebnisse verarbeiten
|
||||
for hits in results_list:
|
||||
for hit in hits:
|
||||
payload = hit.payload or {}
|
||||
target_id = payload.get("note_id")
|
||||
note_id = hit.payload.get("note_id")
|
||||
if not note_id: continue
|
||||
|
||||
if not target_id or target_id in seen_target_ids:
|
||||
# Deduplizierung (Notiz-Ebene)
|
||||
if note_id in seen_target_note_ids:
|
||||
continue
|
||||
|
||||
# Relevanz-Threshold (Modell-spezifisch für nomic)
|
||||
if hit.total_score > 0.55:
|
||||
seen_target_ids.add(target_id)
|
||||
target_type = payload.get("type", "concept")
|
||||
target_title = payload.get("title") or "Unbenannt"
|
||||
# Score Check (Threshold 0.50 für nomic-embed-text)
|
||||
if hit.total_score > 0.50:
|
||||
seen_target_note_ids.add(note_id)
|
||||
|
||||
# WP-24c: Nutzung der Topologie-Engine
|
||||
suggested_kind = self._resolve_edge_type(current_type, target_type)
|
||||
target_title = hit.payload.get("title") or "Unbekannt"
|
||||
|
||||
# INTELLIGENTE KANTEN-LOGIK (MATRIX)
|
||||
# Den Typ der gefundenen Notiz aus dem Payload lesen
|
||||
target_type = hit.payload.get("type", "concept")
|
||||
smart_edge = self._resolve_edge_type(current_type, target_type)
|
||||
|
||||
suggestions.append({
|
||||
"type": "semantic_match",
|
||||
"text_found": (hit.source.get("text") or "")[:80] + "...",
|
||||
"text_found": (hit.source.get("text") or "")[:60] + "...",
|
||||
"target_title": target_title,
|
||||
"target_id": target_id,
|
||||
"suggested_edge_type": suggested_kind,
|
||||
"suggested_markdown": f"[[rel:{suggested_kind} {target_title}]]",
|
||||
"target_id": note_id,
|
||||
"suggested_edge_type": smart_edge,
|
||||
"suggested_markdown": f"[[rel:{smart_edge} {target_title}]]",
|
||||
"confidence": round(hit.total_score, 2),
|
||||
"reason": f"Semantischer Bezug zu {target_type} ({int(hit.total_score*100)}%)"
|
||||
"reason": f"Semantisch ähnlich zu {target_type} ({hit.total_score:.2f})"
|
||||
})
|
||||
|
||||
# Sortierung nach Konfidenz
|
||||
# Sortieren nach Confidence
|
||||
suggestions.sort(key=lambda x: x["confidence"], reverse=True)
|
||||
|
||||
return {
|
||||
"draft_length": len(text),
|
||||
"analyzed_windows": len(search_queries),
|
||||
"suggestions_count": len(suggestions),
|
||||
"suggestions": suggestions[:12] # Top 12 Vorschläge
|
||||
"suggestions": suggestions[:10]
|
||||
}
|
||||
|
||||
# --- LOGIK-ZENTRALE (WP-24c) ---
|
||||
# ---------------------------------------------------------
|
||||
# Core Logic: Die Matrix
|
||||
# ---------------------------------------------------------
|
||||
|
||||
def _resolve_edge_type(self, source_type: str, target_type: str) -> str:
|
||||
"""
|
||||
Ermittelt den optimalen Kanten-Typ zwischen zwei Notiz-Typen.
|
||||
Nutzt EdgeRegistry (graph_schema.md) statt lokaler Matrix.
|
||||
Entscheidungsmatrix für komplexe Verbindungen.
|
||||
Definiert, wie Typ A auf Typ B verlinken sollte.
|
||||
"""
|
||||
# 1. Spezifische Prüfung: Gibt es eine Regel für Source -> Target?
|
||||
info = edge_registry.get_topology_info(source_type, target_type)
|
||||
typical = info.get("typical", [])
|
||||
if typical:
|
||||
return typical[0] # Erster Vorschlag aus dem Schema
|
||||
st = source_type.lower()
|
||||
tt = target_type.lower()
|
||||
|
||||
# 2. Fallback: Was ist für den Quell-Typ generell typisch? (Source -> any)
|
||||
info_fallback = edge_registry.get_topology_info(source_type, "any")
|
||||
typical_fallback = info_fallback.get("typical", [])
|
||||
if typical_fallback:
|
||||
return typical_fallback[0]
|
||||
# Regeln für 'experience' (Erfahrungen)
|
||||
if st == "experience":
|
||||
if tt == "value": return "based_on"
|
||||
if tt == "principle": return "derived_from"
|
||||
if tt == "trip": return "part_of"
|
||||
if tt == "lesson": return "learned"
|
||||
if tt == "project": return "related_to" # oder belongs_to
|
||||
|
||||
# 3. Globaler Fallback (Sicherheitsnetz)
|
||||
return "related_to"
|
||||
# Regeln für 'project'
|
||||
if st == "project":
|
||||
if tt == "decision": return "depends_on"
|
||||
if tt == "concept": return "uses"
|
||||
if tt == "person": return "managed_by"
|
||||
|
||||
# --- HELPERS (VOLLSTÄNDIG ERHALTEN) ---
|
||||
# Regeln für 'decision' (ADR)
|
||||
if st == "decision":
|
||||
if tt == "principle": return "compliant_with"
|
||||
if tt == "requirement": return "addresses"
|
||||
|
||||
# Fallback: Standard aus der types.yaml für den Source-Typ
|
||||
return self._get_default_edge_type(st)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Sliding Windows
|
||||
# ---------------------------------------------------------
|
||||
|
||||
def _generate_search_queries(self, text: str) -> List[str]:
|
||||
"""Erzeugt überlappende Fenster für die Vektorsuche (Sliding Window)."""
|
||||
"""
|
||||
Erzeugt intelligente Fenster + Footer Scan.
|
||||
"""
|
||||
text_len = len(text)
|
||||
if not text: return []
|
||||
|
||||
queries = []
|
||||
|
||||
# Fokus A: Dokument-Anfang (Kontext)
|
||||
# 1. Start / Gesamtkontext
|
||||
queries.append(text[:600])
|
||||
|
||||
# Fokus B: Dokument-Ende (Aktueller Schreibfokus)
|
||||
if text_len > 250:
|
||||
footer = text[-350:]
|
||||
# 2. Footer-Scan (Wichtig für "Projekt"-Referenzen am Ende)
|
||||
if text_len > 150:
|
||||
footer = text[-250:]
|
||||
if footer not in queries:
|
||||
queries.append(footer)
|
||||
|
||||
# Fokus C: Zwischenabschnitte bei langen Texten
|
||||
if text_len > 1200:
|
||||
# 3. Sliding Window für lange Texte
|
||||
if text_len > 800:
|
||||
window_size = 500
|
||||
step = 1200
|
||||
for i in range(600, text_len - 400, step):
|
||||
chunk = text[i:i+window_size]
|
||||
step = 1500
|
||||
for i in range(window_size, text_len - window_size, step):
|
||||
end_pos = min(i + window_size, text_len)
|
||||
chunk = text[i:end_pos]
|
||||
if len(chunk) > 100:
|
||||
queries.append(chunk)
|
||||
|
||||
return queries
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Standard Helpers
|
||||
# ---------------------------------------------------------
|
||||
|
||||
async def _get_semantic_suggestions_async(self, text: str):
|
||||
"""Führt eine asynchrone Vektorsuche über den Retriever aus."""
|
||||
req = QueryRequest(query=text, top_k=6, explain=False)
|
||||
req = QueryRequest(query=text, top_k=5, explain=False)
|
||||
try:
|
||||
# Nutzt hybrid_retrieve (WP-15b Standard)
|
||||
res = hybrid_retrieve(req)
|
||||
return res.results
|
||||
except Exception as e:
|
||||
logger.error(f"Discovery retrieval error: {e}")
|
||||
logger.error(f"Semantic suggestion error: {e}")
|
||||
return []
|
||||
|
||||
def _load_type_registry(self) -> dict:
|
||||
"""Lädt die types.yaml für Typ-Definitionen."""
|
||||
path = os.getenv("MINDNET_TYPES_FILE", "config/types.yaml")
|
||||
if not os.path.exists(path):
|
||||
return {}
|
||||
if os.path.exists("types.yaml"): path = "types.yaml"
|
||||
else: return {}
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
except Exception:
|
||||
return {}
|
||||
with open(path, "r", encoding="utf-8") as f: return yaml.safe_load(f) or {}
|
||||
except Exception: return {}
|
||||
|
||||
def _get_default_edge_type(self, note_type: str) -> str:
|
||||
types_cfg = self.registry.get("types", {})
|
||||
type_def = types_cfg.get(note_type, {})
|
||||
defaults = type_def.get("edge_defaults")
|
||||
return defaults[0] if defaults else "related_to"
|
||||
|
||||
def _fetch_all_titles_and_aliases(self) -> List[Dict]:
|
||||
"""Holt alle Note-IDs, Titel und Aliasse für den Exakt-Match Abgleich."""
|
||||
entities = []
|
||||
notes = []
|
||||
next_page = None
|
||||
col = f"{self.prefix}_notes"
|
||||
try:
|
||||
|
|
@ -203,40 +225,30 @@ class DiscoveryService:
|
|||
for point in res:
|
||||
pl = point.payload or {}
|
||||
aliases = pl.get("aliases") or []
|
||||
if isinstance(aliases, str):
|
||||
aliases = [aliases]
|
||||
if isinstance(aliases, str): aliases = [aliases]
|
||||
|
||||
entities.append({
|
||||
notes.append({
|
||||
"id": pl.get("note_id"),
|
||||
"title": pl.get("title"),
|
||||
"aliases": aliases,
|
||||
"type": pl.get("type", "concept")
|
||||
"type": pl.get("type", "concept") # WICHTIG: Typ laden für Matrix
|
||||
})
|
||||
if next_page is None:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning(f"Error fetching entities for discovery: {e}")
|
||||
return entities
|
||||
if next_page is None: break
|
||||
except Exception: pass
|
||||
return notes
|
||||
|
||||
def _find_entities_in_text(self, text: str, entities: List[Dict]) -> List[Dict]:
|
||||
"""Sucht im Text nach Erwähnungen bekannter Entitäten."""
|
||||
found = []
|
||||
text_lower = text.lower()
|
||||
for entity in entities:
|
||||
# Title Check
|
||||
title = entity.get("title")
|
||||
# Titel-Check
|
||||
if title and title.lower() in text_lower:
|
||||
found.append({
|
||||
"match": title, "title": title,
|
||||
"id": entity["id"], "type": entity["type"]
|
||||
})
|
||||
found.append({"match": title, "title": title, "id": entity["id"], "type": entity["type"]})
|
||||
continue
|
||||
# Alias-Check
|
||||
# Alias Check
|
||||
for alias in entity.get("aliases", []):
|
||||
if str(alias).lower() in text_lower:
|
||||
found.append({
|
||||
"match": str(alias), "title": title,
|
||||
"id": entity["id"], "type": entity["type"]
|
||||
})
|
||||
found.append({"match": alias, "title": title, "id": entity["id"], "type": entity["type"]})
|
||||
break
|
||||
return found
|
||||
|
|
@ -1,17 +1,21 @@
|
|||
"""
|
||||
FILE: app/services/edge_registry.py
|
||||
DESCRIPTION: Single Source of Truth für Kanten-Typen, Symmetrien und Graph-Topologie.
|
||||
WP-24c: Implementierung der dualen Registry (Vocabulary & Schema).
|
||||
Unterstützt dynamisches Laden von Inversen und kontextuellen Vorschlägen.
|
||||
VERSION: 1.0.1 (WP-24c: Verified Atomic Topology)
|
||||
DESCRIPTION: Single Source of Truth für Kanten-Typen mit dynamischem Reload.
|
||||
WP-15b: Erweiterte Provenance-Prüfung für die Candidate-Validation.
|
||||
Sichert die Graph-Integrität durch strikte Trennung von System- und Inhaltskanten.
|
||||
WP-22: Fix für absolute Pfade außerhalb des Vaults (Prod-Dictionary).
|
||||
WP-20: Synchronisation mit zentralen Settings (v0.6.2).
|
||||
VERSION: 0.8.0
|
||||
STATUS: Active
|
||||
DEPENDENCIES: re, os, json, logging, time, app.config
|
||||
LAST_ANALYSIS: 2025-12-26
|
||||
"""
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Dict, Optional, Set, Tuple, List
|
||||
from typing import Dict, Optional, Set, Tuple
|
||||
|
||||
from app.config import get_settings
|
||||
|
||||
|
|
@ -19,12 +23,11 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
class EdgeRegistry:
|
||||
"""
|
||||
Zentraler Verwalter für das Kanten-Vokabular und das Graph-Schema.
|
||||
Singleton-Pattern zur Sicherstellung konsistenter Validierung.
|
||||
Zentraler Verwalter für das Kanten-Vokabular.
|
||||
Implementiert das Singleton-Pattern für konsistente Validierung über alle Services.
|
||||
"""
|
||||
_instance = None
|
||||
|
||||
# SYSTEM-SCHUTZ: Diese Kanten sind für die strukturelle Integrität reserviert (v0.8.0 Erhalt)
|
||||
# System-Kanten, die nicht durch User oder KI gesetzt werden dürfen
|
||||
FORBIDDEN_SYSTEM_EDGES = {"next", "prev", "belongs_to"}
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
|
|
@ -39,189 +42,124 @@ class EdgeRegistry:
|
|||
|
||||
settings = get_settings()
|
||||
|
||||
# --- Pfad-Konfiguration (WP-24c: Variable Pfade für Vault-Spiegelung) ---
|
||||
# Das Vokabular (Semantik)
|
||||
# 1. Pfad aus den zentralen Settings laden (WP-20 Synchronisation)
|
||||
# Priorisiert den Pfad aus der .env / config.py (v0.6.2)
|
||||
self.full_vocab_path = os.path.abspath(settings.MINDNET_VOCAB_PATH)
|
||||
|
||||
# Das Schema (Topologie) - Konfigurierbar via ENV: MINDNET_SCHEMA_PATH
|
||||
schema_env = getattr(settings, "MINDNET_SCHEMA_PATH", None)
|
||||
if schema_env:
|
||||
self.full_schema_path = os.path.abspath(schema_env)
|
||||
else:
|
||||
# Fallback: Liegt im selben Verzeichnis wie das Vokabular
|
||||
self.full_schema_path = os.path.join(os.path.dirname(self.full_vocab_path), "graph_schema.md")
|
||||
|
||||
self.unknown_log_path = "data/logs/unknown_edges.jsonl"
|
||||
|
||||
# --- Interne Datenspeicher ---
|
||||
self.canonical_map: Dict[str, str] = {}
|
||||
self.inverse_map: Dict[str, str] = {}
|
||||
self.valid_types: Set[str] = set()
|
||||
self._last_mtime = 0.0
|
||||
|
||||
# Topologie: source_type -> { target_type -> {"typical": set, "prohibited": set} }
|
||||
self.topology: Dict[str, Dict[str, Dict[str, Set[str]]]] = {}
|
||||
|
||||
self._last_vocab_mtime = 0.0
|
||||
self._last_schema_mtime = 0.0
|
||||
|
||||
logger.info(f">>> [EDGE-REGISTRY] Initializing WP-24c Dual-Engine")
|
||||
logger.info(f" - Vocab-Path: {self.full_vocab_path}")
|
||||
logger.info(f" - Schema-Path: {self.full_schema_path}")
|
||||
|
||||
# Initialer Ladevorgang
|
||||
logger.info(f">>> [EDGE-REGISTRY] Initializing with Path: {self.full_vocab_path}")
|
||||
self.ensure_latest()
|
||||
self.initialized = True
|
||||
|
||||
def ensure_latest(self):
|
||||
"""Prüft Zeitstempel beider Dateien und führt bei Änderung Hot-Reload durch."""
|
||||
"""
|
||||
Prüft den Zeitstempel der Vokabular-Datei und lädt bei Bedarf neu.
|
||||
Verhindert Inkonsistenzen bei Laufzeit-Updates des Dictionaries.
|
||||
"""
|
||||
if not os.path.exists(self.full_vocab_path):
|
||||
logger.error(f"!!! [EDGE-REGISTRY ERROR] File not found: {self.full_vocab_path} !!!")
|
||||
return
|
||||
|
||||
try:
|
||||
# Vokabular-Reload bei Änderung
|
||||
if os.path.exists(self.full_vocab_path):
|
||||
v_mtime = os.path.getmtime(self.full_vocab_path)
|
||||
if v_mtime > self._last_vocab_mtime:
|
||||
current_mtime = os.path.getmtime(self.full_vocab_path)
|
||||
if current_mtime > self._last_mtime:
|
||||
self._load_vocabulary()
|
||||
self._last_vocab_mtime = v_mtime
|
||||
|
||||
# Schema-Reload bei Änderung
|
||||
if os.path.exists(self.full_schema_path):
|
||||
s_mtime = os.path.getmtime(self.full_schema_path)
|
||||
if s_mtime > self._last_schema_mtime:
|
||||
self._load_schema()
|
||||
self._last_schema_mtime = s_mtime
|
||||
|
||||
self._last_mtime = current_mtime
|
||||
except Exception as e:
|
||||
logger.error(f"!!! [EDGE-REGISTRY] Sync failure: {e}")
|
||||
logger.error(f"!!! [EDGE-REGISTRY] Error checking file time: {e}")
|
||||
|
||||
def _load_vocabulary(self):
|
||||
"""Parst edge_vocabulary.md: | Canonical | Inverse | Aliases | Description |"""
|
||||
"""
|
||||
Parst das Markdown-Wörterbuch und baut die Canonical-Map auf.
|
||||
Erkennt Tabellen-Strukturen und extrahiert fettgedruckte System-Typen.
|
||||
"""
|
||||
self.canonical_map.clear()
|
||||
self.inverse_map.clear()
|
||||
self.valid_types.clear()
|
||||
|
||||
# Regex für die 4-Spalten Struktur (WP-24c konform)
|
||||
# Erwartet: | **`type`** | `inverse` | alias1, alias2 | ... |
|
||||
pattern = re.compile(r"\|\s*\*\*`?([a-zA-Z0-9_-]+)`?\*\*\s*\|\s*`?([a-zA-Z0-9_-]+)`?\s*\|\s*([^|]+)\|")
|
||||
# Regex für Tabellen-Struktur: | **Typ** | Aliase |
|
||||
pattern = re.compile(r"\|\s*\*\*`?([a-zA-Z0-9_-]+)`?\*\*\s*\|\s*([^|]+)\|")
|
||||
|
||||
try:
|
||||
with open(self.full_vocab_path, "r", encoding="utf-8") as f:
|
||||
c_count = 0
|
||||
c_types, c_aliases = 0, 0
|
||||
for line in f:
|
||||
match = pattern.search(line)
|
||||
if match:
|
||||
canonical = match.group(1).strip().lower()
|
||||
inverse = match.group(2).strip().lower()
|
||||
aliases_raw = match.group(3).strip()
|
||||
aliases_str = match.group(2).strip()
|
||||
|
||||
self.valid_types.add(canonical)
|
||||
self.canonical_map[canonical] = canonical
|
||||
if inverse:
|
||||
self.inverse_map[canonical] = inverse
|
||||
c_types += 1
|
||||
|
||||
# Aliase verarbeiten (Normalisierung auf snake_case)
|
||||
if aliases_raw and "Kein Alias" not in aliases_raw:
|
||||
aliases = [a.strip() for a in aliases_raw.split(",") if a.strip()]
|
||||
if aliases_str and "Kein Alias" not in aliases_str:
|
||||
aliases = [a.strip() for a in aliases_str.split(",") if a.strip()]
|
||||
for alias in aliases:
|
||||
# Normalisierung: Kleinschreibung, Underscores statt Leerzeichen
|
||||
clean_alias = alias.replace("`", "").lower().strip().replace(" ", "_")
|
||||
if clean_alias:
|
||||
self.canonical_map[clean_alias] = canonical
|
||||
c_count += 1
|
||||
c_aliases += 1
|
||||
|
||||
logger.info(f"=== [EDGE-REGISTRY SUCCESS] Loaded {c_types} Canonical Types and {c_aliases} Aliases ===")
|
||||
|
||||
logger.info(f"✅ [VOCAB] Loaded {c_count} edge definitions and their inverses.")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ [VOCAB ERROR] {e}")
|
||||
|
||||
def _load_schema(self):
|
||||
"""Parst graph_schema.md: ## Source: `type` | Target | Typical | Prohibited |"""
|
||||
self.topology.clear()
|
||||
current_source = None
|
||||
|
||||
try:
|
||||
with open(self.full_schema_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
# Header erkennen (Atomare Sektionen)
|
||||
src_match = re.search(r"## Source:\s*`?([a-zA-Z0-9_-]+)`?", line)
|
||||
if src_match:
|
||||
current_source = src_match.group(1).strip().lower()
|
||||
if current_source not in self.topology:
|
||||
self.topology[current_source] = {}
|
||||
continue
|
||||
|
||||
# Tabellenzeilen parsen
|
||||
if current_source and "|" in line and not line.startswith("|-") and "Target" not in line:
|
||||
cols = [c.strip().replace("`", "").lower() for c in line.split("|")]
|
||||
if len(cols) >= 4:
|
||||
target_type = cols[1]
|
||||
typical_edges = [e.strip() for e in cols[2].split(",") if e.strip() and e != "-"]
|
||||
prohibited_edges = [e.strip() for e in cols[3].split(",") if e.strip() and e != "-"]
|
||||
|
||||
if target_type not in self.topology[current_source]:
|
||||
self.topology[current_source][target_type] = {"typical": set(), "prohibited": set()}
|
||||
|
||||
self.topology[current_source][target_type]["typical"].update(typical_edges)
|
||||
self.topology[current_source][target_type]["prohibited"].update(prohibited_edges)
|
||||
|
||||
logger.info(f"✅ [SCHEMA] Topology matrix built for {len(self.topology)} source types.")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ [SCHEMA ERROR] {e}")
|
||||
logger.error(f"!!! [EDGE-REGISTRY FATAL] Error reading file: {e} !!!")
|
||||
|
||||
def resolve(self, edge_type: str, provenance: str = "explicit", context: dict = None) -> str:
|
||||
"""
|
||||
Löst Aliasse auf kanonische Namen auf und schützt System-Kanten.
|
||||
Erhalt der v0.8.0 Schutz-Logik.
|
||||
WP-15b: Validiert einen Kanten-Typ gegen das Vokabular und prüft Berechtigungen.
|
||||
Sichert, dass nur strukturelle Prozesse System-Kanten setzen dürfen.
|
||||
"""
|
||||
self.ensure_latest()
|
||||
if not edge_type:
|
||||
return "related_to"
|
||||
|
||||
# Normalisierung des Typs
|
||||
clean_type = edge_type.lower().strip().replace(" ", "_").replace("-", "_")
|
||||
ctx = context or {}
|
||||
|
||||
# Sicherheits-Gate: Schutz vor unerlaubter Nutzung von System-Kanten
|
||||
# WP-15b: System-Kanten dürfen weder manuell noch durch KI/Vererbung gesetzt werden.
|
||||
# Nur Provenienz 'structure' (interne Prozesse) ist autorisiert.
|
||||
# Wir blockieren hier alle Provenienzen außer 'structure'.
|
||||
restricted_provenance = ["explicit", "semantic_ai", "inherited", "global_pool", "rule"]
|
||||
if provenance in restricted_provenance and clean_type in self.FORBIDDEN_SYSTEM_EDGES:
|
||||
self._log_issue(clean_type, f"forbidden_system_edge_manipulation_by_{provenance}", ctx)
|
||||
self._log_issue(clean_type, f"forbidden_usage_by_{provenance}", ctx)
|
||||
return "related_to"
|
||||
|
||||
# System-Kanten sind NUR bei struktureller Provenienz (Code-generiert) erlaubt
|
||||
# System-Kanten sind NUR bei struktureller Provenienz erlaubt
|
||||
if provenance == "structure" and clean_type in self.FORBIDDEN_SYSTEM_EDGES:
|
||||
return clean_type
|
||||
|
||||
# Alias-Auflösung
|
||||
return self.canonical_map.get(clean_type, clean_type)
|
||||
# Mapping auf kanonischen Namen (Alias-Auflösung)
|
||||
if clean_type in self.canonical_map:
|
||||
return self.canonical_map[clean_type]
|
||||
|
||||
def get_inverse(self, edge_type: str) -> str:
|
||||
"""WP-24c: Gibt das symmetrische Gegenstück zurück."""
|
||||
canonical = self.resolve(edge_type)
|
||||
return self.inverse_map.get(canonical, "related_to")
|
||||
|
||||
def get_topology_info(self, source_type: str, target_type: str) -> Dict[str, List[str]]:
|
||||
"""
|
||||
WP-24c: Liefert kontextuelle Kanten-Empfehlungen für Obsidian und das Backend.
|
||||
"""
|
||||
self.ensure_latest()
|
||||
|
||||
# Hierarchische Suche: Spezifisch -> 'any' -> Empty
|
||||
src_cfg = self.topology.get(source_type, self.topology.get("any", {}))
|
||||
tgt_cfg = src_cfg.get(target_type, src_cfg.get("any", {"typical": set(), "prohibited": set()}))
|
||||
|
||||
return {
|
||||
"typical": sorted(list(tgt_cfg["typical"])),
|
||||
"prohibited": sorted(list(tgt_cfg["prohibited"]))
|
||||
}
|
||||
# Fallback und Logging unbekannter Typen für Admin-Review
|
||||
self._log_issue(clean_type, "unknown_type", ctx)
|
||||
return clean_type
|
||||
|
||||
def _log_issue(self, edge_type: str, error_kind: str, ctx: dict):
|
||||
"""JSONL-Logging für unbekannte/verbotene Kanten (Erhalt v0.8.0)."""
|
||||
"""Detailliertes JSONL-Logging für die Vokabular-Optimierung."""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self.unknown_log_path), exist_ok=True)
|
||||
entry = {
|
||||
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"edge_type": edge_type,
|
||||
"error": error_kind,
|
||||
"file": ctx.get("file", "unknown"),
|
||||
"line": ctx.get("line", "unknown"),
|
||||
"note_id": ctx.get("note_id", "unknown"),
|
||||
"provenance": ctx.get("provenance", "unknown")
|
||||
}
|
||||
with open(self.unknown_log_path, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(entry) + "\n")
|
||||
except Exception: pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Singleton Export
|
||||
# Singleton Export für systemweiten Zugriff
|
||||
registry = EdgeRegistry()
|
||||
|
|
@ -1,74 +1,40 @@
|
|||
"""
|
||||
FILE: app/services/embeddings_client.py
|
||||
DESCRIPTION: Unified Embedding Client. Nutzt MoE-Profile zur Modellsteuerung.
|
||||
WP-25a: Integration der llm_profiles.yaml für konsistente Vektoren.
|
||||
VERSION: 2.6.0 (WP-25a: MoE & Profile Support)
|
||||
DESCRIPTION: Unified Embedding Client. Nutzt Ollama API (HTTP). Ersetzt lokale sentence-transformers.
|
||||
VERSION: 2.5.0
|
||||
STATUS: Active
|
||||
DEPENDENCIES: httpx, requests, app.config, yaml
|
||||
DEPENDENCIES: httpx, requests, app.config
|
||||
LAST_ANALYSIS: 2025-12-15
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import logging
|
||||
import httpx
|
||||
import requests
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any
|
||||
import requests # Für den synchronen Fallback
|
||||
from typing import List
|
||||
from app.config import get_settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EmbeddingsClient:
|
||||
"""
|
||||
Async Client für Embeddings.
|
||||
Steuerung erfolgt über das 'embedding_expert' Profil in llm_profiles.yaml.
|
||||
Async Client für Embeddings via Ollama.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.settings = get_settings()
|
||||
|
||||
# 1. MoE-Profil laden (WP-25a)
|
||||
self.profile = self._load_embedding_profile()
|
||||
|
||||
# 2. Modell & URL auflösen
|
||||
# Priorität: llm_profiles.yaml -> .env (Legacy) -> Fallback
|
||||
self.model = self.profile.get("model") or os.getenv("MINDNET_EMBEDDING_MODEL")
|
||||
|
||||
provider = self.profile.get("provider", "ollama")
|
||||
if provider == "ollama":
|
||||
self.base_url = self.settings.OLLAMA_URL
|
||||
else:
|
||||
# Platzhalter für zukünftige Cloud-Embedding-Provider
|
||||
self.base_url = os.getenv("MINDNET_OLLAMA_URL", "http://127.0.0.1:11434")
|
||||
self.model = os.getenv("MINDNET_EMBEDDING_MODEL")
|
||||
|
||||
if not self.model:
|
||||
self.model = os.getenv("MINDNET_LLM_MODEL", "phi3:mini")
|
||||
logger.warning(f"⚠️ Kein Embedding-Modell in Profil oder .env gefunden. Fallback auf '{self.model}'.")
|
||||
else:
|
||||
logger.info(f"🧬 Embedding-Experte aktiv: Model='{self.model}' via {provider}")
|
||||
|
||||
def _load_embedding_profile(self) -> Dict[str, Any]:
|
||||
"""Lädt die Konfiguration für den embedding_expert."""
|
||||
path_str = getattr(self.settings, "LLM_PROFILES_PATH", "config/llm_profiles.yaml")
|
||||
path = Path(path_str)
|
||||
if not path.exists():
|
||||
return {}
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
profiles = data.get("profiles", {})
|
||||
return profiles.get("embedding_expert", {})
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to load embedding profile: {e}")
|
||||
return {}
|
||||
logger.warning(f"No MINDNET_EMBEDDING_MODEL set. Fallback to '{self.model}'.")
|
||||
|
||||
async def embed_query(self, text: str) -> List[float]:
|
||||
"""Erzeugt einen Vektor für eine Suchanfrage."""
|
||||
return await self._request_embedding(text)
|
||||
|
||||
async def embed_documents(self, texts: List[str]) -> List[List[float]]:
|
||||
"""Erzeugt Vektoren für einen Batch von Dokumenten."""
|
||||
vectors = []
|
||||
# Längeres Timeout für Batches (WP-20 Resilienz)
|
||||
# Längeres Timeout für Batches
|
||||
async with httpx.AsyncClient(timeout=120.0) as client:
|
||||
for text in texts:
|
||||
vec = await self._request_embedding_with_client(client, text)
|
||||
|
|
@ -76,23 +42,18 @@ class EmbeddingsClient:
|
|||
return vectors
|
||||
|
||||
async def _request_embedding(self, text: str) -> List[float]:
|
||||
"""Interner Request-Handler für Einzelabfragen."""
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
return await self._request_embedding_with_client(client, text)
|
||||
|
||||
async def _request_embedding_with_client(self, client: httpx.AsyncClient, text: str) -> List[float]:
|
||||
"""Führt den HTTP-Call gegen die Embedding-API durch."""
|
||||
if not text or not text.strip():
|
||||
return []
|
||||
|
||||
if not text or not text.strip(): return []
|
||||
url = f"{self.base_url}/api/embeddings"
|
||||
try:
|
||||
# WP-25: Aktuell optimiert für Ollama-API Struktur
|
||||
response = await client.post(url, json={"model": self.model, "prompt": text})
|
||||
response.raise_for_status()
|
||||
return response.json().get("embedding", [])
|
||||
except Exception as e:
|
||||
logger.error(f"Async embedding failed (Model: {self.model}): {e}")
|
||||
logger.error(f"Async embedding failed: {e}")
|
||||
return []
|
||||
|
||||
# ==============================================================================
|
||||
|
|
@ -101,38 +62,27 @@ class EmbeddingsClient:
|
|||
|
||||
def embed_text(text: str) -> List[float]:
|
||||
"""
|
||||
LEGACY/SYNC: Nutzt ebenfalls die Profil-Logik für Konsistenz.
|
||||
Ersetzt lokale sentence-transformers zur Vermeidung von Dimensionskonflikten.
|
||||
LEGACY/SYNC: Nutzt jetzt ebenfalls OLLAMA via 'requests'.
|
||||
Ersetzt SentenceTransformers, um Dimensionskonflikte (768 vs 384) zu lösen.
|
||||
"""
|
||||
if not text or not text.strip():
|
||||
return []
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
# Schneller Profil-Lookup für Sync-Mode
|
||||
path = Path(getattr(settings, "LLM_PROFILES_PATH", "config/llm_profiles.yaml"))
|
||||
base_url = os.getenv("MINDNET_OLLAMA_URL", "http://127.0.0.1:11434")
|
||||
model = os.getenv("MINDNET_EMBEDDING_MODEL")
|
||||
base_url = settings.OLLAMA_URL
|
||||
|
||||
if path.exists():
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
prof = data.get("profiles", {}).get("embedding_expert", {})
|
||||
if prof.get("model"):
|
||||
model = prof["model"]
|
||||
except: pass
|
||||
|
||||
# Fallback logik identisch zur Klasse
|
||||
if not model:
|
||||
model = os.getenv("MINDNET_LLM_MODEL", "phi3:mini")
|
||||
|
||||
url = f"{base_url}/api/embeddings"
|
||||
|
||||
try:
|
||||
# Synchroner Request via requests
|
||||
# Synchroner Request (blockierend)
|
||||
response = requests.post(url, json={"model": model, "prompt": text}, timeout=30)
|
||||
response.raise_for_status()
|
||||
return response.json().get("embedding", [])
|
||||
data = response.json()
|
||||
return data.get("embedding", [])
|
||||
except Exception as e:
|
||||
logger.error(f"Sync embedding failed (Model: {model}): {e}")
|
||||
logger.error(f"Sync embedding (Ollama) failed: {e}")
|
||||
return []
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
"""
|
||||
FILE: app/services/llm_service.py
|
||||
DESCRIPTION: Hybrid-Client für Ollama, Google GenAI (Gemini) und OpenRouter.
|
||||
WP-25b: Implementierung der Lazy-Prompt-Orchestration (Modell-spezifisch).
|
||||
VERSION: 3.5.5 (WP-25b: Prompt Orchestration & Full Resilience)
|
||||
Verwaltet provider-spezifische Prompts und Background-Last.
|
||||
WP-20: Optimiertes Fallback-Management zum Schutz von Cloud-Quoten.
|
||||
WP-20 Fix: Bulletproof Prompt-Auflösung für format() Aufrufe.
|
||||
WP-22/JSON: Optionales JSON-Schema + strict (für OpenRouter structured outputs).
|
||||
FIX: Intelligente Rate-Limit Erkennung (429 Handling), v1-API Sync & Timeouts.
|
||||
VERSION: 3.3.9
|
||||
STATUS: Active
|
||||
FIX:
|
||||
- WP-25b: get_prompt() unterstützt Hierarchie: Model-ID -> Provider -> Default.
|
||||
- WP-25b: generate_raw_response() unterstützt prompt_key + variables für Lazy-Formatting.
|
||||
- WP-25a: Voller Erhalt der rekursiven Fallback-Kaskade und visited_profiles Schutz.
|
||||
- WP-20: Restaurierung des internen Ollama-Retry-Loops für Hardware-Stabilität.
|
||||
- Importiert clean_llm_text von app.core.registry zur Vermeidung von Circular Imports.
|
||||
- Wendet clean_llm_text auf Text-Antworten in generate_raw_response an.
|
||||
"""
|
||||
import httpx
|
||||
import yaml
|
||||
|
|
@ -17,25 +19,26 @@ import asyncio
|
|||
import json
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
from openai import AsyncOpenAI
|
||||
from openai import AsyncOpenAI # Für OpenRouter (OpenAI-kompatibel)
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any, Literal
|
||||
from app.config import get_settings
|
||||
|
||||
# Import der neutralen Bereinigungs-Logik
|
||||
# ENTSCHEIDENDER FIX: Import der neutralen Bereinigungs-Logik (WP-14)
|
||||
from app.core.registry import clean_llm_text
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LLMService:
|
||||
# GLOBALER SEMAPHOR für Hintergrund-Last Steuerung (WP-06)
|
||||
_background_semaphore = None
|
||||
|
||||
def __init__(self):
|
||||
self.settings = get_settings()
|
||||
self.prompts = self._load_prompts()
|
||||
self.profiles = self._load_llm_profiles()
|
||||
self._decision_engine = None
|
||||
|
||||
# Initialisiere Semaphore einmalig auf Klassen-Ebene
|
||||
if LLMService._background_semaphore is None:
|
||||
limit = getattr(self.settings, "BACKGROUND_LIMIT", 2)
|
||||
logger.info(f"🚦 LLMService: Initializing Background Semaphore with limit: {limit}")
|
||||
|
|
@ -47,9 +50,10 @@ class LLMService:
|
|||
timeout=httpx.Timeout(self.settings.LLM_TIMEOUT)
|
||||
)
|
||||
|
||||
# 2. Google GenAI Client
|
||||
# 2. Google GenAI Client (Modern SDK)
|
||||
self.google_client = None
|
||||
if self.settings.GOOGLE_API_KEY:
|
||||
# FIX: Wir erzwingen api_version 'v1' für höhere Stabilität bei 2.5er Modellen.
|
||||
self.google_client = genai.Client(
|
||||
api_key=self.settings.GOOGLE_API_KEY,
|
||||
http_options={'api_version': 'v1'}
|
||||
|
|
@ -62,20 +66,16 @@ class LLMService:
|
|||
self.openrouter_client = AsyncOpenAI(
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
api_key=self.settings.OPENROUTER_API_KEY,
|
||||
# Strikter Timeout für OpenRouter Free-Tier zur Vermeidung von Hangs.
|
||||
timeout=45.0
|
||||
)
|
||||
logger.info("🛰️ LLMService: OpenRouter Integration active.")
|
||||
|
||||
@property
|
||||
def decision_engine(self):
|
||||
if self._decision_engine is None:
|
||||
from app.core.retrieval.decision_engine import DecisionEngine
|
||||
self._decision_engine = DecisionEngine()
|
||||
return self._decision_engine
|
||||
|
||||
def _load_prompts(self) -> dict:
|
||||
"""Lädt die Prompt-Konfiguration aus der YAML-Datei."""
|
||||
path = Path(self.settings.PROMPTS_PATH)
|
||||
if not path.exists():
|
||||
logger.error(f"❌ Prompts file not found at {path}")
|
||||
return {}
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
|
|
@ -84,49 +84,30 @@ class LLMService:
|
|||
logger.error(f"❌ Failed to load prompts: {e}")
|
||||
return {}
|
||||
|
||||
def _load_llm_profiles(self) -> dict:
|
||||
"""WP-25a: Lädt die zentralen MoE-Profile aus der llm_profiles.yaml."""
|
||||
path_str = getattr(self.settings, "LLM_PROFILES_PATH", "config/llm_profiles.yaml")
|
||||
path = Path(path_str)
|
||||
if not path.exists():
|
||||
logger.warning(f"⚠️ LLM Profiles file not found at {path}.")
|
||||
return {}
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
return data.get("profiles", {})
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to load llm_profiles.yaml: {e}")
|
||||
return {}
|
||||
|
||||
def get_prompt(self, key: str, model_id: str = None, provider: str = None) -> str:
|
||||
def get_prompt(self, key: str, provider: str = None) -> str:
|
||||
"""
|
||||
WP-25b: Hochpräziser Prompt-Lookup mit detailliertem Trace-Logging.
|
||||
Hole provider-spezifisches Template mit intelligenter Text-Kaskade.
|
||||
HINWEIS: Dies ist nur ein Text-Lookup und verbraucht kein API-Kontingent.
|
||||
Kaskade: Gewählter Provider -> Gemini (Cloud-Stil) -> Ollama (Basis-Stil).
|
||||
"""
|
||||
active_provider = provider or self.settings.MINDNET_LLM_PROVIDER
|
||||
data = self.prompts.get(key, "")
|
||||
if not isinstance(data, dict):
|
||||
|
||||
if isinstance(data, dict):
|
||||
# Wir versuchen erst den Provider, dann Gemini, dann Ollama
|
||||
val = data.get(active_provider, data.get("gemini", data.get("ollama", "")))
|
||||
|
||||
# Falls val durch YAML-Fehler immer noch ein Dict ist, extrahiere ersten String
|
||||
if isinstance(val, dict):
|
||||
logger.warning(f"⚠️ [LLMService] Nested dictionary detected for key '{key}'. Using first entry.")
|
||||
val = next(iter(val.values()), "") if val else ""
|
||||
return str(val)
|
||||
|
||||
return str(data)
|
||||
|
||||
# 1. Spezifischstes Match: Exakte Modell-ID
|
||||
if model_id and model_id in data:
|
||||
logger.info(f"🎯 [PROMPT-TRACE] Level 1 Match: Model-specific ('{model_id}') for key '{key}'")
|
||||
return str(data[model_id])
|
||||
|
||||
# 2. Mittlere Ebene: Provider
|
||||
if provider and provider in data:
|
||||
logger.info(f"📡 [PROMPT-TRACE] Level 2 Match: Provider-fallback ('{provider}') for key '{key}'")
|
||||
return str(data[provider])
|
||||
|
||||
# 3. Globaler Fallback
|
||||
default_val = data.get("default", data.get("gemini", data.get("ollama", "")))
|
||||
logger.info(f"⚓ [PROMPT-TRACE] Level 3 Match: Global Default for key '{key}'")
|
||||
return str(default_val)
|
||||
|
||||
async def generate_raw_response(
|
||||
self,
|
||||
prompt: str = None,
|
||||
prompt_key: str = None, # WP-25b: Lazy Loading Key
|
||||
variables: dict = None, # WP-25b: Daten für Formatierung
|
||||
prompt: str,
|
||||
system: str = None,
|
||||
force_json: bool = False,
|
||||
max_retries: int = 2,
|
||||
|
|
@ -136,110 +117,51 @@ class LLMService:
|
|||
model_override: Optional[str] = None,
|
||||
json_schema: Optional[Dict[str, Any]] = None,
|
||||
json_schema_name: str = "mindnet_json",
|
||||
strict_json_schema: bool = True,
|
||||
profile_name: Optional[str] = None,
|
||||
visited_profiles: Optional[list] = None
|
||||
strict_json_schema: bool = True
|
||||
) -> str:
|
||||
"""Haupteinstiegspunkt für LLM-Anfragen mit Lazy-Prompt Orchestrierung."""
|
||||
visited_profiles = visited_profiles or []
|
||||
target_provider = provider
|
||||
target_model = model_override
|
||||
target_temp = None
|
||||
fallback_profile = None
|
||||
"""
|
||||
Haupteinstiegspunkt für LLM-Anfragen mit Priorisierung.
|
||||
Wendet die Bereinigung auf Text-Antworten an.
|
||||
"""
|
||||
target_provider = provider or self.settings.MINDNET_LLM_PROVIDER
|
||||
|
||||
# 1. Profil-Auflösung (Mixture of Experts)
|
||||
if profile_name and self.profiles:
|
||||
profile = self.profiles.get(profile_name)
|
||||
if profile:
|
||||
target_provider = profile.get("provider", target_provider)
|
||||
target_model = profile.get("model", target_model)
|
||||
target_temp = profile.get("temperature")
|
||||
fallback_profile = profile.get("fallback_profile")
|
||||
visited_profiles.append(profile_name)
|
||||
logger.info(f"🎭 MoE Dispatch: Profil='{profile_name}' -> Provider='{target_provider}' | Model='{target_model}'")
|
||||
else:
|
||||
logger.warning(f"⚠️ Profil '{profile_name}' nicht in llm_profiles.yaml gefunden!")
|
||||
|
||||
if not target_provider:
|
||||
target_provider = self.settings.MINDNET_LLM_PROVIDER
|
||||
|
||||
# 2. WP-25b: Lazy Prompt Resolving
|
||||
# Wir laden den Prompt erst JETZT, basierend auf dem gerade aktiven Modell.
|
||||
current_prompt = prompt
|
||||
if prompt_key:
|
||||
template = self.get_prompt(prompt_key, model_id=target_model, provider=target_provider)
|
||||
# WP-25b FIX: Validierung des geladenen Prompts
|
||||
if not template or not template.strip():
|
||||
available_keys = list(self.prompts.keys())
|
||||
logger.error(f"❌ Prompt key '{prompt_key}' not found or empty. Available keys: {available_keys[:10]}...")
|
||||
raise ValueError(f"Invalid prompt_key: '{prompt_key}' (not found in prompts.yaml)")
|
||||
|
||||
try:
|
||||
# Formatierung mit den übergebenen Variablen
|
||||
current_prompt = template.format(**(variables or {}))
|
||||
except KeyError as e:
|
||||
logger.error(f"❌ Prompt formatting failed for key '{prompt_key}': Missing variable {e}")
|
||||
raise ValueError(f"Missing variable in prompt '{prompt_key}': {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Prompt formatting failed for key '{prompt_key}': {e}")
|
||||
current_prompt = template # Sicherheits-Fallback
|
||||
|
||||
# 3. Ausführung mit Fehler-Handling für Kaskade
|
||||
try:
|
||||
if priority == "background":
|
||||
async with LLMService._background_semaphore:
|
||||
res = await self._dispatch(
|
||||
target_provider, current_prompt, system, force_json,
|
||||
max_retries, base_delay, target_model,
|
||||
json_schema, json_schema_name, strict_json_schema, target_temp
|
||||
target_provider, prompt, system, force_json,
|
||||
max_retries, base_delay, model_override,
|
||||
json_schema, json_schema_name, strict_json_schema
|
||||
)
|
||||
else:
|
||||
# WP-14 Fix: Bereinige Text-Antworten vor Rückgabe
|
||||
return clean_llm_text(res) if not force_json else res
|
||||
|
||||
res = await self._dispatch(
|
||||
target_provider, current_prompt, system, force_json,
|
||||
max_retries, base_delay, target_model,
|
||||
json_schema, json_schema_name, strict_json_schema, target_temp
|
||||
target_provider, prompt, system, force_json,
|
||||
max_retries, base_delay, model_override,
|
||||
json_schema, json_schema_name, strict_json_schema
|
||||
)
|
||||
|
||||
# Check auf leere Cloud-Antworten (WP-25 Stability)
|
||||
if not res and target_provider != "ollama":
|
||||
logger.warning(f"⚠️ Empty response from {target_provider}. Triggering fallback.")
|
||||
raise ValueError(f"Empty response from {target_provider}")
|
||||
|
||||
# WP-14 Fix: Bereinige Text-Antworten vor Rückgabe
|
||||
return clean_llm_text(res) if not force_json else res
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error during execution of profile '{profile_name}' ({target_provider}): {e}")
|
||||
|
||||
# 4. WP-25b Kaskaden-Logik (Rekursiv mit Modell-spezifischem Re-Loading)
|
||||
if fallback_profile and fallback_profile not in visited_profiles:
|
||||
logger.info(f"🔄 Switching to fallback profile: '{fallback_profile}'")
|
||||
return await self.generate_raw_response(
|
||||
prompt=prompt,
|
||||
prompt_key=prompt_key,
|
||||
variables=variables, # Ermöglicht neues Formatting für Fallback-Modell
|
||||
system=system, force_json=force_json,
|
||||
max_retries=max_retries, base_delay=base_delay,
|
||||
priority=priority, provider=None, model_override=None,
|
||||
json_schema=json_schema, json_schema_name=json_schema_name,
|
||||
strict_json_schema=strict_json_schema,
|
||||
profile_name=fallback_profile,
|
||||
visited_profiles=visited_profiles
|
||||
)
|
||||
|
||||
# 5. Ultimativer Notanker: Falls alles fehlschlägt, direkt zu Ollama
|
||||
if target_provider != "ollama" and self.settings.LLM_FALLBACK_ENABLED:
|
||||
logger.warning(f"🚨 Kaskade erschöpft. Nutze finalen Ollama-Notanker.")
|
||||
res = await self._execute_ollama(current_prompt, system, force_json, max_retries, base_delay, target_temp, target_model)
|
||||
return clean_llm_text(res) if not force_json else res
|
||||
|
||||
raise e
|
||||
|
||||
async def _dispatch(
|
||||
self, provider, prompt, system, force_json, max_retries, base_delay,
|
||||
model_override, json_schema, json_schema_name, strict_json_schema, temperature
|
||||
self,
|
||||
provider: str,
|
||||
prompt: str,
|
||||
system: Optional[str],
|
||||
force_json: bool,
|
||||
max_retries: int,
|
||||
base_delay: float,
|
||||
model_override: Optional[str],
|
||||
json_schema: Optional[Dict[str, Any]],
|
||||
json_schema_name: str,
|
||||
strict_json_schema: bool
|
||||
) -> str:
|
||||
"""Routet die Anfrage an den spezifischen Provider-Executor."""
|
||||
"""
|
||||
Routet die Anfrage mit intelligenter Rate-Limit Erkennung.
|
||||
Nutzt max_retries um die Rate-Limit Schleife zu begrenzen.
|
||||
"""
|
||||
rate_limit_attempts = 0
|
||||
# FIX: Wir nutzen max_retries als Limit für Rate-Limit Versuche, wenn explizit klein gewählt (z.B. Chat)
|
||||
max_rate_retries = min(max_retries, getattr(self.settings, "LLM_RATE_LIMIT_RETRIES", 3))
|
||||
wait_time = getattr(self.settings, "LLM_RATE_LIMIT_WAIT", 60.0)
|
||||
|
||||
|
|
@ -247,74 +169,114 @@ class LLMService:
|
|||
try:
|
||||
if provider == "openrouter" and self.openrouter_client:
|
||||
return await self._execute_openrouter(
|
||||
prompt=prompt, system=system, force_json=force_json,
|
||||
model_override=model_override, json_schema=json_schema,
|
||||
json_schema_name=json_schema_name, strict_json_schema=strict_json_schema,
|
||||
temperature=temperature
|
||||
prompt=prompt,
|
||||
system=system,
|
||||
force_json=force_json,
|
||||
model_override=model_override,
|
||||
json_schema=json_schema,
|
||||
json_schema_name=json_schema_name,
|
||||
strict_json_schema=strict_json_schema
|
||||
)
|
||||
|
||||
if provider == "gemini" and self.google_client:
|
||||
return await self._execute_google(prompt, system, force_json, model_override, temperature)
|
||||
return await self._execute_google(prompt, system, force_json, model_override)
|
||||
|
||||
return await self._execute_ollama(prompt, system, force_json, max_retries, base_delay, temperature, model_override)
|
||||
# Default/Fallback zu Ollama
|
||||
return await self._execute_ollama(prompt, system, force_json, max_retries, base_delay)
|
||||
|
||||
except Exception as e:
|
||||
err_str = str(e)
|
||||
if any(x in err_str for x in ["429", "RESOURCE_EXHAUSTED", "rate_limited"]):
|
||||
# Intelligente 429 Erkennung
|
||||
is_rate_limit = any(x in err_str for x in ["429", "RESOURCE_EXHAUSTED", "rate_limited", "Too Many Requests"])
|
||||
|
||||
if is_rate_limit and rate_limit_attempts < max_rate_retries:
|
||||
rate_limit_attempts += 1
|
||||
logger.warning(f"⏳ Rate Limit {provider}. Attempt {rate_limit_attempts}. Wait {wait_time}s.")
|
||||
logger.warning(
|
||||
f"⏳ [LLMService] Rate Limit detected from {provider}. "
|
||||
f"Attempt {rate_limit_attempts}/{max_rate_retries}. Waiting {wait_time}s..."
|
||||
)
|
||||
await asyncio.sleep(wait_time)
|
||||
continue
|
||||
|
||||
# Wenn kein Rate-Limit oder Retries erschöpft -> Fallback zu Ollama (falls aktiviert)
|
||||
if self.settings.LLM_FALLBACK_ENABLED and provider != "ollama":
|
||||
logger.warning(
|
||||
f"🔄 Provider {provider} failed ({err_str}). Falling back to LOCAL OLLAMA."
|
||||
)
|
||||
return await self._execute_ollama(prompt, system, force_json, max_retries, base_delay)
|
||||
raise e
|
||||
|
||||
async def _execute_google(self, prompt, system, force_json, model_override, temperature):
|
||||
model = (model_override or self.settings.GEMINI_MODEL).replace("models/", "")
|
||||
config_kwargs = {
|
||||
"system_instruction": system,
|
||||
"response_mime_type": "application/json" if force_json else "text/plain"
|
||||
}
|
||||
if temperature is not None:
|
||||
config_kwargs["temperature"] = temperature
|
||||
async def _execute_google(self, prompt, system, force_json, model_override):
|
||||
"""Native Google SDK Integration (Gemini) mit v1 Fix."""
|
||||
model = model_override or self.settings.GEMINI_MODEL
|
||||
clean_model = model.replace("models/", "")
|
||||
|
||||
config = types.GenerateContentConfig(**config_kwargs)
|
||||
config = types.GenerateContentConfig(
|
||||
system_instruction=system,
|
||||
response_mime_type="application/json" if force_json else "text/plain"
|
||||
)
|
||||
response = await asyncio.wait_for(
|
||||
asyncio.to_thread(self.google_client.models.generate_content, model=model, contents=prompt, config=config),
|
||||
asyncio.to_thread(
|
||||
self.google_client.models.generate_content,
|
||||
model=clean_model, contents=prompt, config=config
|
||||
),
|
||||
timeout=45.0
|
||||
)
|
||||
return response.text.strip()
|
||||
|
||||
async def _execute_openrouter(self, prompt, system, force_json, model_override, json_schema, json_schema_name, strict_json_schema, temperature) -> str:
|
||||
async def _execute_openrouter(
|
||||
self,
|
||||
prompt: str,
|
||||
system: Optional[str],
|
||||
force_json: bool,
|
||||
model_override: Optional[str],
|
||||
json_schema: Optional[Dict[str, Any]] = None,
|
||||
json_schema_name: str = "mindnet_json",
|
||||
strict_json_schema: bool = True
|
||||
) -> str:
|
||||
"""OpenRouter API Integration (OpenAI-kompatibel)."""
|
||||
model = model_override or self.settings.OPENROUTER_MODEL
|
||||
logger.info(f"🛰️ OpenRouter Call: Model='{model}' | Temp={temperature}")
|
||||
messages = []
|
||||
if system: messages.append({"role": "system", "content": system})
|
||||
if system:
|
||||
messages.append({"role": "system", "content": system})
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
kwargs: Dict[str, Any] = {}
|
||||
if temperature is not None: kwargs["temperature"] = temperature
|
||||
|
||||
if force_json:
|
||||
if json_schema:
|
||||
kwargs["response_format"] = {"type": "json_schema", "json_schema": {"name": json_schema_name, "strict": strict_json_schema, "schema": json_schema}}
|
||||
kwargs["response_format"] = {
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": json_schema_name,
|
||||
"strict": strict_json_schema,
|
||||
"schema": json_schema
|
||||
}
|
||||
}
|
||||
else:
|
||||
kwargs["response_format"] = {"type": "json_object"}
|
||||
|
||||
response = await self.openrouter_client.chat.completions.create(model=model, messages=messages, **kwargs)
|
||||
if not response.choices: return ""
|
||||
return response.choices[0].message.content.strip() if response.choices[0].message.content else ""
|
||||
|
||||
async def _execute_ollama(self, prompt, system, force_json, max_retries, base_delay, temperature=None, model_override=None):
|
||||
# WP-20: Restaurierter Retry-Loop für lokale Hardware-Resilienz
|
||||
effective_model = model_override or self.settings.LLM_MODEL
|
||||
effective_temp = temperature if temperature is not None else (0.1 if force_json else 0.7)
|
||||
response = await self.openrouter_client.chat.completions.create(
|
||||
model=model,
|
||||
messages=messages,
|
||||
**kwargs
|
||||
)
|
||||
return response.choices[0].message.content.strip()
|
||||
|
||||
async def _execute_ollama(self, prompt, system, force_json, max_retries, base_delay):
|
||||
"""Lokaler Ollama Call mit striktem Retry-Limit."""
|
||||
payload = {
|
||||
"model": effective_model,
|
||||
"prompt": prompt, "stream": False,
|
||||
"options": {"temperature": effective_temp, "num_ctx": 8192}
|
||||
"model": self.settings.LLM_MODEL,
|
||||
"prompt": prompt,
|
||||
"stream": False,
|
||||
"options": {
|
||||
"temperature": 0.1 if force_json else 0.7,
|
||||
"num_ctx": 8192 # Begrenzung für Stabilität (WP-20)
|
||||
}
|
||||
if force_json: payload["format"] = "json"
|
||||
if system: payload["system"] = system
|
||||
}
|
||||
if force_json:
|
||||
payload["format"] = "json"
|
||||
if system:
|
||||
payload["system"] = system
|
||||
|
||||
attempt = 0
|
||||
while True:
|
||||
|
|
@ -324,14 +286,32 @@ class LLMService:
|
|||
return res.json().get("response", "").strip()
|
||||
except Exception as e:
|
||||
attempt += 1
|
||||
# WICHTIG: Wenn max_retries=0 (Chat), bricht dies nach dem 1. Versuch (attempt=1) sofort ab.
|
||||
if attempt > max_retries:
|
||||
logger.error(f"❌ Ollama failure after {attempt} attempts: {e}")
|
||||
logger.error(f"❌ Ollama request failed after {attempt} attempt(s): {e}")
|
||||
raise e
|
||||
await asyncio.sleep(base_delay * (2 ** (attempt - 1)))
|
||||
|
||||
async def generate_rag_response(self, query: str, context_str: Optional[str] = None) -> str:
|
||||
return await self.decision_engine.ask(query)
|
||||
wait_time = base_delay * (2 ** (attempt - 1))
|
||||
logger.warning(f"⚠️ Ollama attempt {attempt} failed. Retrying in {wait_time}s...")
|
||||
await asyncio.sleep(wait_time)
|
||||
|
||||
async def generate_rag_response(self, query: str, context_str: str) -> str:
|
||||
"""Vollständiges RAG Chat-Interface."""
|
||||
provider = self.settings.MINDNET_LLM_PROVIDER
|
||||
system_prompt = self.get_prompt("system_prompt", provider)
|
||||
rag_template = self.get_prompt("rag_template", provider)
|
||||
|
||||
final_prompt = rag_template.format(context_str=context_str, query=query)
|
||||
|
||||
# RAG Aufrufe im Chat nutzen nun standardmäßig max_retries=2 (überschreibbar)
|
||||
# Durch den Aufruf von generate_raw_response wird die Bereinigung automatisch angewendet.
|
||||
return await self.generate_raw_response(
|
||||
final_prompt,
|
||||
system=system_prompt,
|
||||
priority="realtime"
|
||||
)
|
||||
|
||||
async def close(self):
|
||||
"""Schließt die HTTP-Verbindungen."""
|
||||
if self.ollama_client:
|
||||
await self.ollama_client.aclose()
|
||||
|
|
@ -1,141 +1,145 @@
|
|||
# config/decision_engine.yaml
|
||||
# VERSION: 3.2.2 (WP-25a: Decoupled MoE Logic)
|
||||
# Steuerung der Decision Engine (Intent Recognition & Graph Routing)
|
||||
# VERSION: 2.6.1 (WP-20: Hybrid LLM & WP-22: Semantic Graph Routing)
|
||||
# STATUS: Active
|
||||
# DESCRIPTION: Zentrale Orchestrierung der Multi-Stream-Engine.
|
||||
# FIX:
|
||||
# - Auslagerung der LLM-Profile in llm_profiles.yaml zur zentralen Wartbarkeit.
|
||||
# - Integration von compression_thresholds zur Inhaltsverdichtung (WP-25a).
|
||||
# - 100% Erhalt aller WP-25 Edge-Boosts und Filter-Typen (v3.1.6).
|
||||
# DoD: Keine Hardcoded Modelle, volle Integration der strategischen Boosts.
|
||||
|
||||
version: 3.2
|
||||
version: 2.6
|
||||
|
||||
settings:
|
||||
llm_fallback_enabled: true
|
||||
# "auto" nutzt den globalen Default-Provider aus der .env
|
||||
router_provider: "auto"
|
||||
# Verweis auf den Intent-Klassifizierer in der prompts.yaml
|
||||
router_prompt_key: "intent_router_v1"
|
||||
# Pfad zur neuen Experten-Konfiguration (WP-25a Architektur-Cleanliness)
|
||||
profiles_config_path: "config/llm_profiles.yaml"
|
||||
router_profile: "compression_fast"
|
||||
|
||||
# --- EBENE 1: STREAM-LIBRARY (Bausteine basierend auf types.yaml v2.7.0) ---
|
||||
streams_library:
|
||||
values_stream:
|
||||
name: "Identität & Ethik"
|
||||
# Referenz auf Experten-Profil (z.B. lokal via Ollama für Privacy)
|
||||
llm_profile: "identity_safe"
|
||||
compression_profile: "identity_safe"
|
||||
compression_threshold: 2500
|
||||
query_template: "Welche meiner Werte und Prinzipien betreffen: {query}"
|
||||
filter_types: ["value", "principle", "belief", "trait", "boundary", "need", "motivation"]
|
||||
top_k: 5
|
||||
edge_boosts:
|
||||
guides: 3.0
|
||||
depends_on: 2.5
|
||||
based_on: 2.0
|
||||
upholds: 2.5
|
||||
violates: 2.5
|
||||
aligned_with: 2.0
|
||||
conflicts_with: 2.0
|
||||
supports: 1.5
|
||||
contradicts: 1.5
|
||||
facts_stream:
|
||||
name: "Operative Realität"
|
||||
llm_profile: "synthesis_pro"
|
||||
compression_profile: "compression_fast"
|
||||
compression_threshold: 3500
|
||||
query_template: "Status, Ressourcen und Fakten zu: {query}"
|
||||
filter_types: ["project", "decision", "task", "goal", "event", "state"]
|
||||
top_k: 5
|
||||
# Strategie für den Router selbst (Welches Modell erkennt den Intent?)
|
||||
# "auto" nutzt den in MINDNET_LLM_PROVIDER gesetzten Standard (z.B. openrouter).
|
||||
router_provider: "auto"
|
||||
|
||||
# Few-Shot Prompting für den LLM-Router
|
||||
llm_router_prompt: |
|
||||
Du bist der zentrale Intent-Klassifikator für Mindnet, einen digitalen Zwilling.
|
||||
Analysiere die Nachricht und wähle die passende Strategie.
|
||||
Antworte NUR mit dem Namen der Strategie.
|
||||
|
||||
STRATEGIEN:
|
||||
- INTERVIEW: User will Wissen erfassen, Notizen anlegen oder Dinge festhalten.
|
||||
- DECISION: Rat, Strategie, Abwägung von Werten, "Soll ich tun X?".
|
||||
- EMPATHY: Gefühle, Reflexion der eigenen Verfassung, Frust, Freude.
|
||||
- CODING: Code-Erstellung, Debugging, technische Dokumentation.
|
||||
- FACT: Reine Wissensabfrage, Definitionen, Suchen von Informationen.
|
||||
|
||||
BEISPIELE:
|
||||
User: "Wie funktioniert die Qdrant-Vektor-DB?" -> FACT
|
||||
User: "Soll ich mein Startup jetzt verkaufen?" -> DECISION
|
||||
User: "Notiere mir kurz meine Gedanken zum Meeting." -> INTERVIEW
|
||||
User: "Ich fühle mich heute sehr erschöpft." -> EMPATHY
|
||||
User: "Schreibe eine FastAPI-Route für den Ingest." -> CODING
|
||||
|
||||
NACHRICHT: "{query}"
|
||||
|
||||
STRATEGIE:
|
||||
|
||||
strategies:
|
||||
# 1. Fakten-Abfrage (Turbo-Modus via OpenRouter / Primary)
|
||||
FACT:
|
||||
description: "Reine Wissensabfrage."
|
||||
preferred_provider: "openrouter"
|
||||
trigger_keywords: []
|
||||
inject_types: []
|
||||
# WP-22: Definitionen & Hierarchien im Graphen bevorzugen
|
||||
edge_boosts:
|
||||
part_of: 2.0
|
||||
depends_on: 1.5
|
||||
implemented_in: 1.5
|
||||
|
||||
biography_stream:
|
||||
name: "Persönliche Erfahrung"
|
||||
llm_profile: "synthesis_pro"
|
||||
compression_profile: "compression_fast"
|
||||
compression_threshold: 3000
|
||||
query_template: "Welche Erlebnisse habe ich im Kontext von {query} gemacht?"
|
||||
filter_types: ["experience", "journal", "profile", "person"]
|
||||
top_k: 3
|
||||
edge_boosts:
|
||||
related_to: 1.5
|
||||
experienced_in: 2.0
|
||||
expert_for: 2.5
|
||||
followed_by: 2.0
|
||||
preceded_by: 2.0
|
||||
|
||||
risk_stream:
|
||||
name: "Risiko-Radar"
|
||||
llm_profile: "synthesis_pro"
|
||||
compression_profile: "compression_fast"
|
||||
compression_threshold: 2500
|
||||
query_template: "Gefahren, Hindernisse oder Risiken bei: {query}"
|
||||
filter_types: ["risk", "obstacle", "bias"]
|
||||
top_k: 3
|
||||
edge_boosts:
|
||||
blocks: 2.5
|
||||
impacts: 2.0
|
||||
risk_of: 2.5
|
||||
|
||||
tech_stream:
|
||||
name: "Wissen & Technik"
|
||||
llm_profile: "tech_expert"
|
||||
compression_profile: "compression_fast"
|
||||
compression_threshold: 4500
|
||||
query_template: "Inhaltliche Details und Definitionen zu: {query}"
|
||||
filter_types: ["concept", "source", "glossary", "idea", "insight", "skill", "habit"]
|
||||
top_k: 5
|
||||
edge_boosts:
|
||||
uses: 2.5
|
||||
implemented_in: 3.0
|
||||
|
||||
# --- EBENE 2: STRATEGIEN (Finale Komposition via MoE-Profile) ---
|
||||
strategies:
|
||||
FACT_WHEN:
|
||||
description: "Abfrage von exakten Zeitpunkten und Terminen."
|
||||
llm_profile: "synthesis_pro"
|
||||
trigger_keywords: ["wann", "datum", "uhrzeit", "zeitpunkt"]
|
||||
use_streams: ["facts_stream", "biography_stream", "tech_stream"]
|
||||
prompt_template: "fact_synthesis_v1"
|
||||
|
||||
FACT_WHAT:
|
||||
description: "Abfrage von Definitionen, Listen und Inhalten."
|
||||
llm_profile: "synthesis_pro"
|
||||
trigger_keywords: ["was ist", "welche sind", "liste", "übersicht", "zusammenfassung"]
|
||||
use_streams: ["facts_stream", "tech_stream", "biography_stream"]
|
||||
prompt_template: "fact_synthesis_v1"
|
||||
composed_of: 2.0
|
||||
similar_to: 1.5
|
||||
caused_by: 0.5
|
||||
prompt_template: "rag_template"
|
||||
prepend_instruction: null
|
||||
|
||||
# 2. Entscheidungs-Frage (Power-Strategie via Gemini)
|
||||
DECISION:
|
||||
description: "Der User sucht Rat, Strategie oder Abwägung."
|
||||
llm_profile: "synthesis_pro"
|
||||
trigger_keywords: ["soll ich", "sollte ich", "entscheidung", "abwägen", "priorität", "empfehlung"]
|
||||
use_streams: ["values_stream", "facts_stream", "risk_stream"]
|
||||
prompt_template: "decision_synthesis_v1"
|
||||
preferred_provider: "gemini"
|
||||
trigger_keywords:
|
||||
- "soll ich"
|
||||
- "meinung"
|
||||
- "besser"
|
||||
- "empfehlung"
|
||||
- "strategie"
|
||||
- "entscheidung"
|
||||
- "abwägung"
|
||||
- "vergleich"
|
||||
inject_types: ["value", "principle", "goal", "risk"]
|
||||
# WP-22: Risiken und Konsequenzen im Graphen priorisieren
|
||||
edge_boosts:
|
||||
blocks: 2.5
|
||||
solves: 2.0
|
||||
depends_on: 1.5
|
||||
risk_of: 2.5
|
||||
impacts: 2.0
|
||||
prompt_template: "decision_template"
|
||||
prepend_instruction: |
|
||||
!!! ENTSCHEIDUNGS-MODUS (AGENTIC MULTI-STREAM) !!!
|
||||
Analysiere die Fakten vor dem Hintergrund meiner Werte und evaluiere die Risiken.
|
||||
Wäge ab, ob das Vorhaben mit meiner langfristigen Identität kompatibel ist.
|
||||
!!! ENTSCHEIDUNGS-MODUS (HYBRID AI) !!!
|
||||
BITTE WÄGE FAKTEN GEGEN FOLGENDE WERTE, PRINZIPIEN UND ZIELE AB:
|
||||
|
||||
# 3. Empathie / "Ich"-Modus (Lokal & Privat via Ollama)
|
||||
EMPATHY:
|
||||
description: "Reaktion auf emotionale Zustände."
|
||||
llm_profile: "synthesis_pro"
|
||||
trigger_keywords: ["fühle", "traurig", "glücklich", "stress", "angst"]
|
||||
use_streams: ["biography_stream", "values_stream"]
|
||||
preferred_provider: "openrouter"
|
||||
trigger_keywords:
|
||||
- "ich fühle"
|
||||
- "traurig"
|
||||
- "glücklich"
|
||||
- "gestresst"
|
||||
- "angst"
|
||||
- "nervt"
|
||||
- "überfordert"
|
||||
- "müde"
|
||||
inject_types: ["experience", "belief", "profile"]
|
||||
edge_boosts:
|
||||
based_on: 2.0
|
||||
related_to: 2.0
|
||||
experienced_in: 2.5
|
||||
blocks: 0.1
|
||||
prompt_template: "empathy_template"
|
||||
prepend_instruction: null
|
||||
|
||||
# 4. Coding / Technical (Gemini Power)
|
||||
CODING:
|
||||
description: "Technische Anfragen und Programmierung."
|
||||
llm_profile: "tech_expert"
|
||||
trigger_keywords: ["code", "python", "script", "bug", "syntax"]
|
||||
use_streams: ["tech_stream", "facts_stream"]
|
||||
preferred_provider: "gemini"
|
||||
trigger_keywords:
|
||||
- "code"
|
||||
- "python"
|
||||
- "script"
|
||||
- "funktion"
|
||||
- "bug"
|
||||
- "syntax"
|
||||
- "json"
|
||||
- "yaml"
|
||||
- "bash"
|
||||
inject_types: ["snippet", "reference", "source"]
|
||||
# WP-22: Technische Abhängigkeiten priorisieren
|
||||
edge_boosts:
|
||||
uses: 2.5
|
||||
depends_on: 2.0
|
||||
implemented_in: 3.0
|
||||
prompt_template: "technical_template"
|
||||
prepend_instruction: null
|
||||
|
||||
# 5. Interview / Datenerfassung (Lokal)
|
||||
INTERVIEW:
|
||||
description: "Der User möchte Wissen erfassen (Eingabemodus)."
|
||||
llm_profile: "compression_fast"
|
||||
use_streams: []
|
||||
description: "Der User möchte Wissen erfassen."
|
||||
preferred_provider: "openrouter"
|
||||
trigger_keywords:
|
||||
- "neue notiz"
|
||||
- "etwas notieren"
|
||||
- "festhalten"
|
||||
- "erstellen"
|
||||
- "dokumentieren"
|
||||
- "anlegen"
|
||||
- "interview"
|
||||
- "erfassen"
|
||||
- "idee speichern"
|
||||
- "draft"
|
||||
inject_types: []
|
||||
edge_boosts: {}
|
||||
prompt_template: "interview_template"
|
||||
prepend_instruction: null
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# config/llm_profiles.yaml
|
||||
# VERSION: 1.3.0 (WP-25a: Global MoE & Fallback Cascade)
|
||||
# STATUS: Active
|
||||
# DESCRIPTION: Zentrale Definition der LLM-Rollen inkl. Ausfall-Logik (Kaskade).
|
||||
|
||||
profiles:
|
||||
# --- CHAT & SYNTHESE ---
|
||||
# Der "Architekt": Hochwertige Synthese. Fällt bei Fehlern auf den Backup-Cloud-Experten zurück.
|
||||
synthesis_pro:
|
||||
provider: "openrouter"
|
||||
model: "google/gemini-2.0-flash-exp:free"
|
||||
temperature: 0.7
|
||||
fallback_profile: "synthesis_backup"
|
||||
|
||||
# Der "Vize": Leistungsstarkes Modell bei einem anderen Provider (Resilienz).
|
||||
synthesis_backup:
|
||||
provider: "openrouter"
|
||||
model: "meta-llama/llama-3.3-70b-instruct:free"
|
||||
temperature: 0.5
|
||||
fallback_profile: "identity_safe" # Letzte Instanz: Lokal
|
||||
|
||||
# Der "Ingenieur": Fachspezialist für Code. Nutzt bei Ausfall den Generalisten.
|
||||
tech_expert:
|
||||
provider: "openrouter"
|
||||
model: "qwen/qwen-2.5-vl-7b-instruct:free"
|
||||
temperature: 0.3
|
||||
fallback_profile: "synthesis_pro"
|
||||
|
||||
# Der "Dampfhammer": Schnell für Routing und Zusammenfassungen.
|
||||
compression_fast:
|
||||
provider: "openrouter"
|
||||
model: "mistralai/mistral-7b-instruct:free"
|
||||
temperature: 0.1
|
||||
fallback_profile: "identity_safe"
|
||||
|
||||
# --- INGESTION EXPERTEN ---
|
||||
# Spezialist für die Extraktion komplexer Datenstrukturen aus Dokumenten.
|
||||
ingest_extractor:
|
||||
provider: "openrouter"
|
||||
model: "mistralai/mistral-7b-instruct:free"
|
||||
temperature: 0.2
|
||||
fallback_profile: "synthesis_backup"
|
||||
|
||||
# Spezialist für binäre Prüfungen (YES/NO). Muss extrem deterministisch sein.
|
||||
ingest_validator:
|
||||
provider: "openrouter"
|
||||
model: "mistralai/mistral-7b-instruct:free"
|
||||
temperature: 0.0
|
||||
fallback_profile: "compression_fast"
|
||||
|
||||
# --- LOKALER ANKER & PRIVACY ---
|
||||
# Der "Wächter": Lokales Modell für maximale Privatsphäre. Ende der Kaskade.
|
||||
identity_safe:
|
||||
provider: "ollama"
|
||||
model: "phi3:mini"
|
||||
temperature: 0.2
|
||||
# Kein fallback_profile definiert = Terminaler Endpunkt
|
||||
|
||||
# --- EMBEDDING EXPERTE ---
|
||||
# Zentralisierung des Embedding-Modells zur Entfernung aus der .env.
|
||||
embedding_expert:
|
||||
provider: "ollama"
|
||||
model: "nomic-embed-text"
|
||||
dimensions: 768
|
||||
|
|
@ -46,18 +46,3 @@ MINDNET_VOCAB_PATH=/mindnet/vault/mindnet/_system/dictionary/edge_vocabulary.md
|
|||
|
||||
# Change Detection für effiziente Re-Imports
|
||||
MINDNET_CHANGE_DETECTION_MODE=full
|
||||
|
||||
# --- WP-24c v4.2.0: Konfigurierbare Markdown-Header für Edge-Zonen ---
|
||||
# Komma-separierte Liste von Headern für LLM-Validierung
|
||||
# Format: Header1,Header2,Header3
|
||||
MINDNET_LLM_VALIDATION_HEADERS=Unzugeordnete Kanten,Edge Pool,Candidates
|
||||
|
||||
# Header-Ebene für LLM-Validierung (1-6, Default: 3 für ###)
|
||||
MINDNET_LLM_VALIDATION_HEADER_LEVEL=3
|
||||
|
||||
# Komma-separierte Liste von Headern für Note-Scope Zonen
|
||||
# Format: Header1,Header2,Header3
|
||||
MINDNET_NOTE_SCOPE_ZONE_HEADERS=Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen
|
||||
|
||||
# Header-Ebene für Note-Scope Zonen (1-6, Default: 2 für ##)
|
||||
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
|
||||
|
|
@ -1,337 +0,0 @@
|
|||
# config/prompts.yaml — VERSION 3.1.2 (WP-25 Cleanup: Multi-Stream Sync)
|
||||
# STATUS: Active
|
||||
# FIX:
|
||||
# - 100% Wiederherstellung der Ingest- & Validierungslogik (Sektion 5-8).
|
||||
# - Überführung der Kategorien 1-4 in die Multi-Stream Struktur unter Beibehaltung des Inhalts.
|
||||
# - Konsolidierung: Sektion 9 (v3.0.0) wurde in Sektion 1 & 2 integriert (keine Redundanz).
|
||||
|
||||
system_prompt: |
|
||||
Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner.
|
||||
|
||||
DEINE IDENTITÄT:
|
||||
- Du bist nicht nur eine Datenbank, sondern handelst nach MEINEN Werten und Zielen.
|
||||
- Du passt deinen Stil dynamisch an die Situation an (Analytisch, Empathisch oder Technisch).
|
||||
|
||||
DEINE REGELN:
|
||||
1. Deine Antwort muss zu 100% auf dem bereitgestellten KONTEXT basieren.
|
||||
2. Halluziniere keine Fakten, die nicht in den Quellen stehen.
|
||||
3. Antworte auf Deutsch (außer bei Code/Fachbegriffen).
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 1. STANDARD: Fakten & Wissen (Intent: FACT_WHAT / FACT_WHEN)
|
||||
# ---------------------------------------------------------
|
||||
# Ersetzt das alte 'rag_template'. Nutzt jetzt parallele Streams.
|
||||
fact_synthesis_v1:
|
||||
ollama: |
|
||||
WISSENS-STREAMS:
|
||||
=========================================
|
||||
FAKTEN & STATUS:
|
||||
{facts_stream}
|
||||
|
||||
ERFAHRUNG & BIOGRAFIE:
|
||||
{biography_stream}
|
||||
|
||||
WISSEN & TECHNIK:
|
||||
{tech_stream}
|
||||
=========================================
|
||||
|
||||
FRAGE:
|
||||
{query}
|
||||
|
||||
ANWEISUNG:
|
||||
Beantworte die Frage präzise basierend auf den Quellen.
|
||||
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden.
|
||||
Fasse die Informationen zusammen. Sei objektiv und neutral.
|
||||
gemini: |
|
||||
Beantworte die Wissensabfrage "{query}" basierend auf diesen Streams:
|
||||
FAKTEN: {facts_stream}
|
||||
BIOGRAFIE/ERFAHRUNG: {biography_stream}
|
||||
TECHNIK: {tech_stream}
|
||||
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden. Antworte strukturiert und präzise.
|
||||
openrouter: |
|
||||
Synthese der Wissens-Streams für: {query}
|
||||
Inhalt: {facts_stream} | {biography_stream} | {tech_stream}
|
||||
Antworte basierend auf dem bereitgestellten Kontext.
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 2. DECISION: Strategie & Abwägung (Intent: DECISION)
|
||||
# ---------------------------------------------------------
|
||||
# Ersetzt das alte 'decision_template'. Nutzt jetzt parallele Streams.
|
||||
decision_synthesis_v1:
|
||||
ollama: |
|
||||
ENTSCHEIDUNGS-STREAMS:
|
||||
=========================================
|
||||
WERTE & PRINZIPIEN (Identität):
|
||||
{values_stream}
|
||||
|
||||
OPERATIVE FAKTEN (Realität):
|
||||
{facts_stream}
|
||||
|
||||
RISIKO-RADAR (Konsequenzen):
|
||||
{risk_stream}
|
||||
=========================================
|
||||
|
||||
ENTSCHEIDUNGSFRAGE:
|
||||
{query}
|
||||
|
||||
ANWEISUNG:
|
||||
Du agierst als mein Entscheidungs-Partner.
|
||||
1. Analysiere die Faktenlage aus den Quellen.
|
||||
2. Prüfe dies hart gegen meine strategischen Notizen (Werte & Prinzipien).
|
||||
3. Wäge ab: Passt die technische/faktische Lösung zu meinen Werten?
|
||||
|
||||
FORMAT:
|
||||
- **Analyse:** (Kurze Zusammenfassung der Fakten)
|
||||
- **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!)
|
||||
- **Empfehlung:** (Klare Meinung: Ja/No/Vielleicht mit Begründung)
|
||||
gemini: |
|
||||
Agiere als mein strategischer Partner. Analysiere die Frage: {query}
|
||||
Werte: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}.
|
||||
Wäge ab und gib eine klare strategische Empfehlung ab.
|
||||
openrouter: |
|
||||
Strategische Multi-Stream Analyse für: {query}
|
||||
Werte-Basis: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}
|
||||
Bitte wäge ab und gib eine Empfehlung.
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 3. EMPATHY: Der Spiegel / "Ich"-Modus (Intent: EMPATHY)
|
||||
# ---------------------------------------------------------
|
||||
empathy_template:
|
||||
ollama: |
|
||||
KONTEXT (ERFAHRUNGEN & WERTE):
|
||||
=========================================
|
||||
ERLEBNISSE & BIOGRAFIE:
|
||||
{biography_stream}
|
||||
|
||||
WERTE & BEDÜRFNISSE:
|
||||
{values_stream}
|
||||
=========================================
|
||||
|
||||
SITUATION:
|
||||
{query}
|
||||
|
||||
ANWEISUNG:
|
||||
Du agierst jetzt als mein empathischer Spiegel.
|
||||
1. Versuche nicht sofort, das Problem technisch zu lösen.
|
||||
2. Zeige Verständnis für die Situation basierend auf meinen eigenen Erfahrungen ([EXPERIENCE]) oder Werten, falls im Kontext vorhanden.
|
||||
3. Antworte in der "Ich"-Form oder "Wir"-Form. Sei unterstützend.
|
||||
|
||||
TONFALL:
|
||||
Ruhig, verständnisvoll, reflektiert. Keine Aufzählungszeichen, sondern fließender Text.
|
||||
gemini: "Sei mein digitaler Spiegel für {query}. Kontext: {biography_stream}, {values_stream}"
|
||||
openrouter: "Empathische Reflexion der Situation {query}. Persönlicher Kontext: {biography_stream}, {values_stream}"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 4. TECHNICAL: Der Coder (Intent: CODING)
|
||||
# ---------------------------------------------------------
|
||||
technical_template:
|
||||
ollama: |
|
||||
KONTEXT (WISSEN & PROJEKTE):
|
||||
=========================================
|
||||
TECHNIK & SNIPPETS:
|
||||
{tech_stream}
|
||||
|
||||
PROJEKT-STATUS:
|
||||
{facts_stream}
|
||||
=========================================
|
||||
|
||||
TASK:
|
||||
{query}
|
||||
|
||||
ANWEISUNG:
|
||||
Du bist Senior Developer.
|
||||
1. Ignoriere Smalltalk. Komm sofort zum Punkt.
|
||||
2. Generiere validen, performanten Code basierend auf den Quellen.
|
||||
3. Wenn Quellen fehlen, nutze dein allgemeines Programmierwissen, aber weise darauf hin.
|
||||
|
||||
FORMAT:
|
||||
- Kurze Erklärung des Ansatzes.
|
||||
- Markdown Code-Block (Copy-Paste fertig).
|
||||
- Wichtige Edge-Cases.
|
||||
gemini: "Generiere Code für {query} unter Berücksichtigung von {tech_stream} und {facts_stream}."
|
||||
openrouter: "Technischer Support für {query}. Referenzen: {tech_stream}, Projekt-Kontext: {facts_stream}"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 5. INTERVIEW: Der "One-Shot Extractor" (WP-07)
|
||||
# ---------------------------------------------------------
|
||||
interview_template:
|
||||
ollama: |
|
||||
TASK:
|
||||
Du bist ein professioneller Ghostwriter. Verwandle den "USER INPUT" in eine strukturierte Notiz vom Typ '{target_type}'.
|
||||
|
||||
STRUKTUR (Nutze EXAKT diese Überschriften):
|
||||
{schema_fields}
|
||||
|
||||
USER INPUT:
|
||||
"{query}"
|
||||
|
||||
ANWEISUNG ZUM INHALT:
|
||||
1. Analysiere den Input genau.
|
||||
2. Schreibe die Inhalte unter die passenden Überschriften aus der STRUKTUR-Liste oben.
|
||||
3. STIL: Schreibe flüssig, professionell und in der Ich-Perspektive. Korrigiere Grammatikfehler, aber behalte den persönlichen Ton bei.
|
||||
4. Wenn Informationen für einen Abschnitt fehlen, schreibe nur: "[TODO: Ergänzen]". Erfinde nichts dazu.
|
||||
|
||||
OUTPUT FORMAT (YAML + MARKDOWN):
|
||||
---
|
||||
type: {target_type}
|
||||
status: draft
|
||||
title: (Erstelle einen treffenden, kurzen Titel für den Inhalt)
|
||||
tags: [Tag1, Tag2]
|
||||
---
|
||||
|
||||
# (Wiederhole den Titel hier)
|
||||
|
||||
## (Erster Begriff aus STRUKTUR)
|
||||
(Text...)
|
||||
|
||||
## (Zweiter Begriff aus STRUKTUR)
|
||||
(Text...)
|
||||
gemini: "Extrahiere Daten für {target_type} aus {query}."
|
||||
openrouter: "Strukturiere den Input {query} nach dem Schema {schema_fields} für Typ {target_type}."
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 6. EDGE_ALLOCATION: Kantenfilter (Ingest)
|
||||
# ---------------------------------------------------------
|
||||
edge_allocation_template:
|
||||
ollama: |
|
||||
TASK:
|
||||
Du bist ein strikter Selektor. Du erhältst eine Liste von "Kandidaten-Kanten" (Strings).
|
||||
Wähle jene aus, die inhaltlich im "Textabschnitt" vorkommen oder relevant sind.
|
||||
|
||||
TEXTABSCHNITT:
|
||||
"""
|
||||
{chunk_text}
|
||||
"""
|
||||
|
||||
KANDIDATEN (Auswahl-Pool):
|
||||
{edge_list}
|
||||
|
||||
REGELN:
|
||||
1. Die Kanten haben das Format "typ:ziel". Der "typ" ist variabel und kann ALLES sein.
|
||||
2. Gib NUR die Strings aus der Kandidaten-Liste zurück, die zum Text passen.
|
||||
3. Erfinde KEINE neuen Kanten.
|
||||
4. Antworte als flache JSON-Liste.
|
||||
|
||||
DEIN OUTPUT (JSON):
|
||||
gemini: |
|
||||
TASK: Ordne Kanten einem Textabschnitt zu.
|
||||
ERLAUBTE TYPEN: {valid_types}
|
||||
TEXT: {chunk_text}
|
||||
KANDIDATEN: {edge_list}
|
||||
OUTPUT: STRIKT eine flache JSON-Liste ["typ:ziel"]. Kein Text davor/danach. Wenn nichts: []. Keine Objekte!
|
||||
openrouter: |
|
||||
TASK: Filtere relevante Kanten aus dem Pool.
|
||||
ERLAUBTE TYPEN: {valid_types}
|
||||
TEXT: {chunk_text}
|
||||
POOL: {edge_list}
|
||||
ANWEISUNG: Gib NUR eine flache JSON-Liste von Strings zurück.
|
||||
BEISPIEL: ["kind:target", "kind:target"]
|
||||
REGEL: Kein Text, keine Analyse, keine Kommentare. Wenn nichts passt, gib [] zurück.
|
||||
OUTPUT:
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 7. SMART EDGE ALLOCATION: Extraktion (Ingest)
|
||||
# ---------------------------------------------------------
|
||||
edge_extraction:
|
||||
ollama: |
|
||||
TASK:
|
||||
Du bist ein Wissens-Ingenieur für den digitalen Zwilling 'mindnet'.
|
||||
Deine Aufgabe ist es, semantische Relationen (Kanten) aus dem Text zu extrahieren,
|
||||
die die Hauptnotiz '{note_id}' mit anderen Konzepten verbinden.
|
||||
|
||||
ANWEISUNGEN:
|
||||
1. Identifiziere wichtige Entitäten, Konzepte oder Ereignisse im Text.
|
||||
2. Bestimme die Art der Beziehung (z.B. part_of, uses, related_to, blocks, caused_by).
|
||||
3. Das Ziel (target) muss ein prägnanter Begriff sein.
|
||||
4. Antworte AUSSCHLIESSLICH in validem JSON als Liste von Objekten.
|
||||
|
||||
BEISPIEL:
|
||||
[[ {{"to": "Ziel-Konzept", \"kind\": \"beziehungs_typ\"}} ]]
|
||||
|
||||
TEXT:
|
||||
"""
|
||||
{text}
|
||||
"""
|
||||
|
||||
DEIN OUTPUT (JSON):
|
||||
gemini: |
|
||||
Analysiere '{note_id}'. Extrahiere semantische Beziehungen.
|
||||
ERLAUBTE TYPEN: {valid_types}
|
||||
TEXT: {text}
|
||||
OUTPUT: STRIKT JSON-Array von Objekten: [[{{"to\":\"Ziel\",\"kind\":\"typ\"}}]]. Kein Text davor/danach. Wenn nichts: [].
|
||||
openrouter: |
|
||||
TASK: Extrahiere semantische Relationen für '{note_id}'.
|
||||
ERLAUBTE TYPEN: {valid_types}
|
||||
TEXT: {text}
|
||||
ANWEISUNG: Antworte AUSSCHLIESSLICH mit einem JSON-Array von Objekten.
|
||||
FORMAT: [[{{"to\":\"Ziel-Begriff\",\"kind\":\"typ\"}}]]
|
||||
STRIKTES VERBOT: Schreibe keine Einleitung, keine Analyse und keine Erklärungen.
|
||||
Wenn keine Relationen existieren, antworte NUR mit: []
|
||||
OUTPUT:
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 8. WP-15b: EDGE VALIDATION (Ingest/Validate)
|
||||
# ---------------------------------------------------------
|
||||
edge_validation:
|
||||
gemini: |
|
||||
Bewerte die semantische Validität dieser Verbindung im Wissensgraph.
|
||||
|
||||
KONTEXT DER QUELLE (Chunk):
|
||||
"{chunk_text}"
|
||||
|
||||
ZIEL-NOTIZ: "{target_title}"
|
||||
ZIEL-BESCHREIBUNG (Zusammenfassung):
|
||||
"{target_summary}"
|
||||
|
||||
GEPLANTE RELATION: "{edge_kind}"
|
||||
|
||||
FRAGE: Bestätigt der Kontext der Quelle die Beziehung '{edge_kind}' zum Ziel?
|
||||
REGEL: Antworte NUR mit 'YES' oder 'NO'. Keine Erklärungen oder Smalltalk.
|
||||
openrouter: |
|
||||
Verify semantic relation for graph construction.
|
||||
Source Context: {chunk_text}
|
||||
Target Note: {target_title}
|
||||
Target Summary: {target_summary}
|
||||
Proposed Relation: {edge_kind}
|
||||
Instruction: Does the source context support this relation to the target?
|
||||
Result: Respond ONLY with 'YES' or 'NO'.
|
||||
ollama: |
|
||||
Bewerte die semantische Korrektheit dieser Verbindung.
|
||||
QUELLE: {chunk_text}
|
||||
ZIEL: {target_title} ({target_summary})
|
||||
BEZIEHUNG: {edge_kind}
|
||||
Ist diese Verbindung valide? Antworte NUR mit YES oder NO.
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 10. WP-25: INTENT ROUTING (Intent: CLASSIFY)
|
||||
# ---------------------------------------------------------
|
||||
intent_router_v1:
|
||||
ollama: |
|
||||
Analysiere die Nutzeranfrage und wähle die passende Strategie.
|
||||
Antworte NUR mit dem Namen der Strategie.
|
||||
|
||||
STRATEGIEN:
|
||||
- FACT_WHEN: Nur für explizite Fragen nach einem exakten Datum, Uhrzeit oder dem "Wann" eines Ereignisses.
|
||||
- FACT_WHAT: Fragen nach Inhalten, Listen von Objekten/Projekten, Definitionen oder "Was/Welche" Anfragen (auch bei Zeiträumen).
|
||||
- DECISION: Rat, Meinung, "Soll ich?", Abwägung gegen Werte.
|
||||
- EMPATHY: Emotionen, Reflexion, Befindlichkeit.
|
||||
- CODING: Programmierung, Skripte, technische Syntax.
|
||||
- INTERVIEW: Dokumentation neuer Informationen, Notizen anlegen.
|
||||
|
||||
NACHRICHT: "{query}"
|
||||
STRATEGIE:
|
||||
gemini: |
|
||||
Classify intent:
|
||||
- FACT_WHEN: Exact dates/times only.
|
||||
- FACT_WHAT: Content, lists of entities (projects, etc.), definitions, "What/Which" queries.
|
||||
- DECISION: Strategic advice/values.
|
||||
- EMPATHY: Emotions.
|
||||
- CODING: Tech/Code.
|
||||
- INTERVIEW: Data entry.
|
||||
Query: "{query}"
|
||||
Result (One word only):
|
||||
openrouter: |
|
||||
Select strategy for Mindnet:
|
||||
FACT_WHEN (timing/dates), FACT_WHAT (entities/lists/what/which), DECISION, EMPATHY, CODING, INTERVIEW.
|
||||
Query: "{query}"
|
||||
Response:
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
# config/prompts.yaml — VERSION 3.2.2 (WP-25b: Hierarchical Model Sync)
|
||||
# STATUS: Active
|
||||
# FIX:
|
||||
# - 100% Erhalt der Original-Prompts aus v3.1.2 für die Provider-Ebene (ollama, gemini, openrouter).
|
||||
# - Integration der Modell-spezifischen Overrides für Gemini 2.0, Llama 3.3 und Qwen 2.5.
|
||||
# - Hinzufügen des notwendigen 'compression_template' für die DecisionEngine v1.3.0.
|
||||
# config/prompts.yaml — Final V2.6.0 (WP-15b Candidate-Validation)
|
||||
# WP-20: Optimierte Cloud-Templates zur Unterdrückung von Modell-Geschwätz.
|
||||
# FIX: Explizite Verbote für Einleitungstexte zur Vermeidung von JSON-Parsing-Fehlern.
|
||||
# WP-15b: Integration der binären edge_validation für den Two-Pass Workflow.
|
||||
# OLLAMA: UNVERÄNDERT laut Benutzeranweisung.
|
||||
|
||||
system_prompt: |
|
||||
Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner.
|
||||
|
|
@ -18,32 +17,13 @@ system_prompt: |
|
|||
3. Antworte auf Deutsch (außer bei Code/Fachbegriffen).
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 1. STANDARD: Fakten & Wissen (Intent: FACT_WHAT / FACT_WHEN)
|
||||
# 1. STANDARD: Fakten & Wissen (Intent: FACT)
|
||||
# ---------------------------------------------------------
|
||||
fact_synthesis_v1:
|
||||
# --- Modell-spezifisch (WP-25b Optimierung) ---
|
||||
"google/gemini-2.0-flash-exp:free": |
|
||||
Analysiere die Wissens-Streams für: {query}
|
||||
FAKTEN: {facts_stream} | BIOGRAFIE: {biography_stream} | TECHNIK: {tech_stream}
|
||||
Nutze deine hohe Reasoning-Kapazität für eine tiefe Synthese. Antworte präzise auf Deutsch.
|
||||
|
||||
"meta-llama/llama-3.3-70b-instruct:free": |
|
||||
Erstelle eine fundierte Synthese für die Frage: "{query}"
|
||||
Nutze die Daten: {facts_stream}, {biography_stream} und {tech_stream}.
|
||||
Trenne klare Fakten von Erfahrungen. Bleibe strikt beim bereitgestellten Kontext.
|
||||
|
||||
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||
rag_template:
|
||||
ollama: |
|
||||
WISSENS-STREAMS:
|
||||
QUELLEN (WISSEN):
|
||||
=========================================
|
||||
FAKTEN & STATUS:
|
||||
{facts_stream}
|
||||
|
||||
ERFAHRUNG & BIOGRAFIE:
|
||||
{biography_stream}
|
||||
|
||||
WISSEN & TECHNIK:
|
||||
{tech_stream}
|
||||
{context_str}
|
||||
=========================================
|
||||
|
||||
FRAGE:
|
||||
|
|
@ -51,45 +31,25 @@ fact_synthesis_v1:
|
|||
|
||||
ANWEISUNG:
|
||||
Beantworte die Frage präzise basierend auf den Quellen.
|
||||
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden.
|
||||
Fasse die Informationen zusammen. Sei objektiv und neutral.
|
||||
|
||||
gemini: |
|
||||
Beantworte die Wissensabfrage "{query}" basierend auf diesen Streams:
|
||||
FAKTEN: {facts_stream}
|
||||
BIOGRAFIE/ERFAHRUNG: {biography_stream}
|
||||
TECHNIK: {tech_stream}
|
||||
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden. Antworte strukturiert und präzise.
|
||||
|
||||
Kontext meines digitalen Zwillings: {context_str}
|
||||
Beantworte strukturiert und präzise: {query}
|
||||
openrouter: |
|
||||
Synthese der Wissens-Streams für: {query}
|
||||
Inhalt: {facts_stream} | {biography_stream} | {tech_stream}
|
||||
Antworte basierend auf dem bereitgestellten Kontext.
|
||||
Kontext-Analyse für den digitalen Zwilling:
|
||||
{context_str}
|
||||
|
||||
default: "Beantworte {query} basierend auf dem Kontext: {facts_stream} {biography_stream} {tech_stream}."
|
||||
Anfrage: {query}
|
||||
Antworte basierend auf dem Kontext.
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 2. DECISION: Strategie & Abwägung (Intent: DECISION)
|
||||
# ---------------------------------------------------------
|
||||
decision_synthesis_v1:
|
||||
# --- Modell-spezifisch (WP-25b Optimierung) ---
|
||||
"google/gemini-2.0-flash-exp:free": |
|
||||
Agiere als strategischer Partner für: {query}
|
||||
WERTE: {values_stream} | FAKTEN: {facts_stream} | RISIKEN: {risk_stream}
|
||||
Prüfe die Fakten gegen meine Werte. Zeige Zielkonflikte auf. Gib eine klare Empfehlung.
|
||||
|
||||
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||
decision_template:
|
||||
ollama: |
|
||||
ENTSCHEIDUNGS-STREAMS:
|
||||
KONTEXT (FAKTEN & STRATEGIE):
|
||||
=========================================
|
||||
WERTE & PRINZIPIEN (Identität):
|
||||
{values_stream}
|
||||
|
||||
OPERATIVE FAKTEN (Realität):
|
||||
{facts_stream}
|
||||
|
||||
RISIKO-RADAR (Konsequenzen):
|
||||
{risk_stream}
|
||||
{context_str}
|
||||
=========================================
|
||||
|
||||
ENTSCHEIDUNGSFRAGE:
|
||||
|
|
@ -98,39 +58,27 @@ decision_synthesis_v1:
|
|||
ANWEISUNG:
|
||||
Du agierst als mein Entscheidungs-Partner.
|
||||
1. Analysiere die Faktenlage aus den Quellen.
|
||||
2. Prüfe dies hart gegen meine strategischen Notizen (Werte & Prinzipien).
|
||||
2. Prüfe dies hart gegen meine strategischen Notizen (Typ [VALUE], [PRINCIPLE], [GOAL]).
|
||||
3. Wäge ab: Passt die technische/faktische Lösung zu meinen Werten?
|
||||
|
||||
FORMAT:
|
||||
- **Analyse:** (Kurze Zusammenfassung der Fakten)
|
||||
- **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!)
|
||||
- **Empfehlung:** (Klare Meinung: Ja/No/Vielleicht mit Begründung)
|
||||
|
||||
gemini: |
|
||||
Agiere als mein strategischer Partner. Analysiere die Frage: {query}
|
||||
Werte: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}.
|
||||
Wäge ab und gib eine klare strategische Empfehlung ab.
|
||||
|
||||
Agiere als strategischer Partner. Analysiere die Frage {query} basierend auf meinen Werten im Kontext {context_str}.
|
||||
openrouter: |
|
||||
Strategische Multi-Stream Analyse für: {query}
|
||||
Werte-Basis: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}
|
||||
Bitte wäge ab und gib eine Empfehlung.
|
||||
|
||||
default: "Prüfe {query} gegen Werte {values_stream} und Fakten {facts_stream}."
|
||||
Strategische Entscheidungsanalyse: {query}
|
||||
Wertebasis aus dem Graphen: {context_str}
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 3. EMPATHY: Der Spiegel / "Ich"-Modus (Intent: EMPATHY)
|
||||
# ---------------------------------------------------------
|
||||
empathy_template:
|
||||
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||
ollama: |
|
||||
KONTEXT (ERFAHRUNGEN & WERTE):
|
||||
KONTEXT (ERFAHRUNGEN & GLAUBENSSÄTZE):
|
||||
=========================================
|
||||
ERLEBNISSE & BIOGRAFIE:
|
||||
{biography_stream}
|
||||
|
||||
WERTE & BEDÜRFNISSE:
|
||||
{values_stream}
|
||||
{context_str}
|
||||
=========================================
|
||||
|
||||
SITUATION:
|
||||
|
|
@ -139,36 +87,22 @@ empathy_template:
|
|||
ANWEISUNG:
|
||||
Du agierst jetzt als mein empathischer Spiegel.
|
||||
1. Versuche nicht sofort, das Problem technisch zu lösen.
|
||||
2. Zeige Verständnis für die Situation basierend auf meinen eigenen Erfahrungen ([EXPERIENCE]) oder Werten, falls im Kontext vorhanden.
|
||||
2. Zeige Verständnis für die Situation basierend auf meinen eigenen Erfahrungen ([EXPERIENCE]) oder Glaubenssätzen ([BELIEF]), falls im Kontext vorhanden.
|
||||
3. Antworte in der "Ich"-Form oder "Wir"-Form. Sei unterstützend.
|
||||
|
||||
TONFALL:
|
||||
Ruhig, verständnisvoll, reflektiert. Keine Aufzählungszeichen, sondern fließender Text.
|
||||
|
||||
gemini: "Sei mein digitaler Spiegel für {query}. Kontext: {biography_stream}, {values_stream}"
|
||||
openrouter: "Empathische Reflexion der Situation {query}. Persönlicher Kontext: {biography_stream}, {values_stream}"
|
||||
|
||||
default: "Reflektiere empathisch über {query} basierend auf {biography_stream}."
|
||||
gemini: "Sei mein digitaler Spiegel für {query}. Kontext: {context_str}"
|
||||
openrouter: "Empathische Reflexion der Situation {query}. Persönlicher Kontext: {context_str}"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 4. TECHNICAL: Der Coder (Intent: CODING)
|
||||
# ---------------------------------------------------------
|
||||
technical_template:
|
||||
# --- Modell-spezifisch (WP-25b Optimierung) ---
|
||||
"qwen/qwen-2.5-vl-7b-instruct:free": |
|
||||
Du bist Senior Software Engineer. TASK: {query}
|
||||
REFERENZEN: {tech_stream} | KONTEXT: {facts_stream}
|
||||
Generiere validen, performanten Code. Nutze die Snippets aus dem Kontext.
|
||||
|
||||
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||
ollama: |
|
||||
KONTEXT (WISSEN & PROJEKTE):
|
||||
KONTEXT (DOCS & SNIPPETS):
|
||||
=========================================
|
||||
TECHNIK & SNIPPETS:
|
||||
{tech_stream}
|
||||
|
||||
PROJEKT-STATUS:
|
||||
{facts_stream}
|
||||
{context_str}
|
||||
=========================================
|
||||
|
||||
TASK:
|
||||
|
|
@ -184,17 +118,13 @@ technical_template:
|
|||
- Kurze Erklärung des Ansatzes.
|
||||
- Markdown Code-Block (Copy-Paste fertig).
|
||||
- Wichtige Edge-Cases.
|
||||
|
||||
gemini: "Generiere Code für {query} unter Berücksichtigung von {tech_stream} und {facts_stream}."
|
||||
openrouter: "Technischer Support für {query}. Referenzen: {tech_stream}, Projekt-Kontext: {facts_stream}"
|
||||
|
||||
default: "Erstelle eine technische Lösung für {query}."
|
||||
gemini: "Generiere Code für {query} unter Berücksichtigung von {context_str}."
|
||||
openrouter: "Technischer Support für {query}. Code-Referenzen: {context_str}"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 5. INTERVIEW: Der "One-Shot Extractor" (WP-07)
|
||||
# 5. INTERVIEW: Der "One-Shot Extractor" (Performance Mode)
|
||||
# ---------------------------------------------------------
|
||||
interview_template:
|
||||
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||
ollama: |
|
||||
TASK:
|
||||
Du bist ein professioneller Ghostwriter. Verwandle den "USER INPUT" in eine strukturierte Notiz vom Typ '{target_type}'.
|
||||
|
|
@ -226,30 +156,11 @@ interview_template:
|
|||
|
||||
## (Zweiter Begriff aus STRUKTUR)
|
||||
(Text...)
|
||||
|
||||
gemini: "Extrahiere Daten für {target_type} aus {query}."
|
||||
openrouter: "Strukturiere den Input {query} nach dem Schema {schema_fields} für Typ {target_type}."
|
||||
|
||||
default: "Extrahiere Informationen für {target_type} aus dem Input: {query}"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 6. WP-25b: PRE-SYNTHESIS COMPRESSION (Neu!)
|
||||
# ---------------------------------------------------------
|
||||
compression_template:
|
||||
"mistralai/mistral-7b-instruct:free": |
|
||||
Reduziere den Stream '{stream_name}' auf die Informationen, die für die Beantwortung der Frage '{query}' absolut notwendig sind.
|
||||
BEHALTE: Harte Fakten, Projektnamen, konkrete Werte und Quellenangaben.
|
||||
ENTFERNE: Redundante Einleitungen, Füllwörter und irrelevante Details.
|
||||
|
||||
INHALT:
|
||||
{content}
|
||||
|
||||
KOMPRIMIERTE ANALYSE:
|
||||
|
||||
default: "Fasse das Wichtigste aus {stream_name} für die Frage {query} kurz zusammen: {content}"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 7. EDGE_ALLOCATION: Kantenfilter (Ingest)
|
||||
# 6. EDGE_ALLOCATION: Kantenfilter (Intent: OFFLINE_FILTER)
|
||||
# ---------------------------------------------------------
|
||||
edge_allocation_template:
|
||||
ollama: |
|
||||
|
|
@ -272,14 +183,12 @@ edge_allocation_template:
|
|||
4. Antworte als flache JSON-Liste.
|
||||
|
||||
DEIN OUTPUT (JSON):
|
||||
|
||||
gemini: |
|
||||
TASK: Ordne Kanten einem Textabschnitt zu.
|
||||
ERLAUBTE TYPEN: {valid_types}
|
||||
TEXT: {chunk_text}
|
||||
KANDIDATEN: {edge_list}
|
||||
OUTPUT: STRIKT eine flache JSON-Liste ["typ:ziel"]. Kein Text davor/danach. Wenn nichts: []. Keine Objekte!
|
||||
|
||||
openrouter: |
|
||||
TASK: Filtere relevante Kanten aus dem Pool.
|
||||
ERLAUBTE TYPEN: {valid_types}
|
||||
|
|
@ -290,10 +199,8 @@ edge_allocation_template:
|
|||
REGEL: Kein Text, keine Analyse, keine Kommentare. Wenn nichts passt, gib [] zurück.
|
||||
OUTPUT:
|
||||
|
||||
default: "[]"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 8. SMART EDGE ALLOCATION: Extraktion (Ingest)
|
||||
# 7. SMART EDGE ALLOCATION: Extraktion (Intent: INGEST)
|
||||
# ---------------------------------------------------------
|
||||
edge_extraction:
|
||||
ollama: |
|
||||
|
|
@ -317,13 +224,11 @@ edge_extraction:
|
|||
"""
|
||||
|
||||
DEIN OUTPUT (JSON):
|
||||
|
||||
gemini: |
|
||||
Analysiere '{note_id}'. Extrahiere semantische Beziehungen.
|
||||
ERLAUBTE TYPEN: {valid_types}
|
||||
TEXT: {text}
|
||||
OUTPUT: STRIKT JSON-Array von Objekten: [[{{"to\":\"Ziel\",\"kind\":\"typ\"}}]]. Kein Text davor/danach. Wenn nichts: [].
|
||||
|
||||
openrouter: |
|
||||
TASK: Extrahiere semantische Relationen für '{note_id}'.
|
||||
ERLAUBTE TYPEN: {valid_types}
|
||||
|
|
@ -334,20 +239,10 @@ edge_extraction:
|
|||
Wenn keine Relationen existieren, antworte NUR mit: []
|
||||
OUTPUT:
|
||||
|
||||
default: "[]"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 9. INGESTION: EDGE VALIDATION (Ingest/Validate)
|
||||
# 8. WP-15b: EDGE VALIDATION (Intent: VALIDATE)
|
||||
# ---------------------------------------------------------
|
||||
edge_validation:
|
||||
# --- Modell-spezifisch (WP-25b Optimierung) ---
|
||||
"mistralai/mistral-7b-instruct:free": |
|
||||
Verify relation '{edge_kind}' for graph integrity.
|
||||
Chunk: "{chunk_text}"
|
||||
Target: "{target_title}" ({target_summary})
|
||||
Respond ONLY with 'YES' or 'NO'.
|
||||
|
||||
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||
gemini: |
|
||||
Bewerte die semantische Validität dieser Verbindung im Wissensgraph.
|
||||
|
||||
|
|
@ -362,7 +257,6 @@ edge_validation:
|
|||
|
||||
FRAGE: Bestätigt der Kontext der Quelle die Beziehung '{edge_kind}' zum Ziel?
|
||||
REGEL: Antworte NUR mit 'YES' oder 'NO'. Keine Erklärungen oder Smalltalk.
|
||||
|
||||
openrouter: |
|
||||
Verify semantic relation for graph construction.
|
||||
Source Context: {chunk_text}
|
||||
|
|
@ -371,83 +265,9 @@ edge_validation:
|
|||
Proposed Relation: {edge_kind}
|
||||
Instruction: Does the source context support this relation to the target?
|
||||
Result: Respond ONLY with 'YES' or 'NO'.
|
||||
|
||||
ollama: |
|
||||
Bewerte die semantische Korrektheit dieser Verbindung.
|
||||
QUELLE: {chunk_text}
|
||||
ZIEL: {target_title} ({target_summary})
|
||||
BEZIEHUNG: {edge_kind}
|
||||
Ist diese Verbindung valide? Antworte NUR mit YES oder NO.
|
||||
|
||||
default: "YES"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 10. WP-25: INTENT ROUTING (Intent: CLASSIFY)
|
||||
# ---------------------------------------------------------
|
||||
intent_router_v1:
|
||||
# --- Modell-spezifisch (WP-25b Optimierung) ---
|
||||
"mistralai/mistral-7b-instruct:free": |
|
||||
Classify query "{query}" into exactly one of these categories:
|
||||
FACT_WHEN, FACT_WHAT, DECISION, EMPATHY, CODING, INTERVIEW.
|
||||
Respond with the category name only.
|
||||
|
||||
# --- EXAKTE Provider-Fallbacks aus v3.1.2 ---
|
||||
ollama: |
|
||||
Analysiere die Nutzeranfrage und wähle die passende Strategie.
|
||||
Antworte NUR mit dem Namen der Strategie.
|
||||
|
||||
STRATEGIEN:
|
||||
- FACT_WHEN: Nur für explizite Fragen nach einem exakten Datum, Uhrzeit oder dem "Wann" eines Ereignisses.
|
||||
- FACT_WHAT: Fragen nach Inhalten, Listen von Objekten/Projekten, Definitionen oder "Was/Welche" Anfragen (auch bei Zeiträumen).
|
||||
- DECISION: Rat, Meinung, "Soll ich?", Abwägung gegen Werte.
|
||||
- EMPATHY: Emotionen, Reflexion, Befindlichkeit.
|
||||
- CODING: Programmierung, Skripte, technische Syntax.
|
||||
- INTERVIEW: Dokumentation neuer Informationen, Notizen anlegen.
|
||||
|
||||
NACHRICHT: "{query}"
|
||||
STRATEGIE:
|
||||
|
||||
gemini: |
|
||||
Classify intent:
|
||||
- FACT_WHEN: Exact dates/times only.
|
||||
- FACT_WHAT: Content, lists of entities (projects, etc.), definitions, "What/Which" queries.
|
||||
- DECISION: Strategic advice/values.
|
||||
- EMPATHY: Emotions.
|
||||
- CODING: Tech/Code.
|
||||
- INTERVIEW: Data entry.
|
||||
Query: "{query}"
|
||||
Result (One word only):
|
||||
|
||||
openrouter: |
|
||||
Select strategy for Mindnet:
|
||||
FACT_WHEN (timing/dates), FACT_WHAT (entities/lists/what/which), DECISION, EMPATHY, CODING, INTERVIEW.
|
||||
Query: "{query}"
|
||||
Response:
|
||||
|
||||
default: "FACT_WHAT"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 11. WP-25b: FALLBACK SYNTHESIS (Error Recovery)
|
||||
# ---------------------------------------------------------
|
||||
fallback_synthesis:
|
||||
ollama: |
|
||||
Beantworte die folgende Frage basierend auf dem bereitgestellten Kontext.
|
||||
|
||||
FRAGE:
|
||||
{query}
|
||||
|
||||
KONTEXT:
|
||||
{context}
|
||||
|
||||
ANWEISUNG:
|
||||
Nutze den Kontext, um eine präzise Antwort zu geben. Falls der Kontext unvollständig ist, weise darauf hin.
|
||||
|
||||
gemini: |
|
||||
Frage: {query}
|
||||
Kontext: {context}
|
||||
Antworte basierend auf dem Kontext.
|
||||
|
||||
openrouter: |
|
||||
Answer the question "{query}" using the provided context: {context}
|
||||
|
||||
default: "Answer: {query}\n\nContext: {context}"
|
||||
|
|
@ -23,6 +23,7 @@ chunking_profiles:
|
|||
overlap: [50, 100]
|
||||
|
||||
# C. SMART FLOW (Text-Fluss)
|
||||
# Nutzt Sliding Window, aber mit LLM-Kanten-Analyse.
|
||||
sliding_smart_edges:
|
||||
strategy: sliding_window
|
||||
enable_smart_edge_allocation: true
|
||||
|
|
@ -31,6 +32,7 @@ chunking_profiles:
|
|||
overlap: [50, 80]
|
||||
|
||||
# D. SMART STRUCTURE (Soft Split)
|
||||
# Trennt bevorzugt an H2, fasst aber kleine Abschnitte zusammen ("Soft Mode").
|
||||
structured_smart_edges:
|
||||
strategy: by_heading
|
||||
enable_smart_edge_allocation: true
|
||||
|
|
@ -41,6 +43,8 @@ chunking_profiles:
|
|||
overlap: [50, 80]
|
||||
|
||||
# E. SMART STRUCTURE STRICT (H2 Hard Split)
|
||||
# Trennt ZWINGEND an jeder H2.
|
||||
# Verhindert, dass "Vater" und "Partner" (Profile) oder Werte verschmelzen.
|
||||
structured_smart_edges_strict:
|
||||
strategy: by_heading
|
||||
enable_smart_edge_allocation: true
|
||||
|
|
@ -51,6 +55,9 @@ chunking_profiles:
|
|||
overlap: [50, 80]
|
||||
|
||||
# F. SMART STRUCTURE DEEP (H3 Hard Split + Merge-Check)
|
||||
# Spezialfall für "Leitbild Prinzipien":
|
||||
# - Trennt H1, H2, H3 hart.
|
||||
# - Aber: Merged "leere" H2 (Tier 2) mit der folgenden H3 (MP1).
|
||||
structured_smart_edges_strict_L3:
|
||||
strategy: by_heading
|
||||
enable_smart_edge_allocation: true
|
||||
|
|
@ -66,17 +73,22 @@ chunking_profiles:
|
|||
defaults:
|
||||
retriever_weight: 1.0
|
||||
chunking_profile: sliding_standard
|
||||
edge_defaults: []
|
||||
|
||||
# ==============================================================================
|
||||
# 3. INGESTION SETTINGS (WP-14 Dynamization)
|
||||
# ==============================================================================
|
||||
# Steuert, welche Notizen verarbeitet werden und wie Fallbacks aussehen.
|
||||
ingestion_settings:
|
||||
# Liste der Status-Werte, die beim Import ignoriert werden sollen.
|
||||
ignore_statuses: ["system", "template", "archive", "hidden"]
|
||||
# Standard-Typ, falls kein Typ im Frontmatter angegeben ist.
|
||||
default_note_type: "concept"
|
||||
|
||||
# ==============================================================================
|
||||
# 4. SUMMARY & SCAN SETTINGS
|
||||
# ==============================================================================
|
||||
# Steuert die Tiefe des Pre-Scans für den Context-Cache.
|
||||
summary_settings:
|
||||
max_summary_length: 500
|
||||
pre_scan_depth: 600
|
||||
|
|
@ -84,6 +96,7 @@ summary_settings:
|
|||
# ==============================================================================
|
||||
# 5. LLM SETTINGS
|
||||
# ==============================================================================
|
||||
# Steuerzeichen und Patterns zur Bereinigung der LLM-Antworten.
|
||||
llm_settings:
|
||||
cleanup_patterns: ["<s>", "</s>", "[OUT]", "[/OUT]", "```json", "```"]
|
||||
|
||||
|
|
@ -94,8 +107,9 @@ llm_settings:
|
|||
types:
|
||||
|
||||
experience:
|
||||
chunking_profile: structured_smart_edges
|
||||
retriever_weight: 1.10
|
||||
chunking_profile: sliding_smart_edges
|
||||
retriever_weight: 1.10 # Erhöht für biografische Relevanz
|
||||
edge_defaults: ["derived_from", "references"]
|
||||
detection_keywords: ["erleben", "reagieren", "handeln", "prägen", "reflektieren"]
|
||||
schema:
|
||||
- "Situation (Was ist passiert?)"
|
||||
|
|
@ -104,8 +118,9 @@ types:
|
|||
- "Reflexion & Learning (Was lerne ich daraus?)"
|
||||
|
||||
insight:
|
||||
chunking_profile: structured_smart_edges
|
||||
retriever_weight: 1.20
|
||||
chunking_profile: sliding_smart_edges
|
||||
retriever_weight: 1.20 # Hoch gewichtet für aktuelle Steuerung
|
||||
edge_defaults: ["references", "based_on"]
|
||||
detection_keywords: ["beobachten", "erkennen", "verstehen", "analysieren", "schlussfolgern"]
|
||||
schema:
|
||||
- "Beobachtung (Was sehe ich?)"
|
||||
|
|
@ -114,8 +129,9 @@ types:
|
|||
- "Handlungsempfehlung"
|
||||
|
||||
project:
|
||||
chunking_profile: structured_smart_edges
|
||||
chunking_profile: sliding_smart_edges
|
||||
retriever_weight: 0.97
|
||||
edge_defaults: ["references", "depends_on"]
|
||||
detection_keywords: ["umsetzen", "planen", "starten", "bauen", "abschließen"]
|
||||
schema:
|
||||
- "Mission & Zielsetzung"
|
||||
|
|
@ -125,6 +141,7 @@ types:
|
|||
decision:
|
||||
chunking_profile: structured_smart_edges_strict
|
||||
retriever_weight: 1.00
|
||||
edge_defaults: ["caused_by", "references"]
|
||||
detection_keywords: ["entscheiden", "wählen", "abwägen", "priorisieren", "festlegen"]
|
||||
schema:
|
||||
- "Kontext & Problemstellung"
|
||||
|
|
@ -132,9 +149,12 @@ types:
|
|||
- "Die Entscheidung"
|
||||
- "Begründung"
|
||||
|
||||
# --- PERSÖNLICHKEIT & IDENTITÄT ---
|
||||
|
||||
value:
|
||||
chunking_profile: structured_smart_edges_strict
|
||||
retriever_weight: 1.00
|
||||
edge_defaults: ["related_to"]
|
||||
detection_keywords: ["werten", "achten", "verpflichten", "bedeuten"]
|
||||
schema:
|
||||
- "Definition"
|
||||
|
|
@ -144,6 +164,7 @@ types:
|
|||
principle:
|
||||
chunking_profile: structured_smart_edges_strict_L3
|
||||
retriever_weight: 0.95
|
||||
edge_defaults: ["derived_from", "references"]
|
||||
detection_keywords: ["leiten", "steuern", "ausrichten", "handhaben"]
|
||||
schema:
|
||||
- "Das Prinzip"
|
||||
|
|
@ -152,6 +173,7 @@ types:
|
|||
trait:
|
||||
chunking_profile: structured_smart_edges_strict
|
||||
retriever_weight: 1.10
|
||||
edge_defaults: ["related_to"]
|
||||
detection_keywords: ["begeistern", "können", "auszeichnen", "befähigen", "stärken"]
|
||||
schema:
|
||||
- "Eigenschaft / Talent"
|
||||
|
|
@ -161,6 +183,7 @@ types:
|
|||
obstacle:
|
||||
chunking_profile: structured_smart_edges_strict
|
||||
retriever_weight: 1.00
|
||||
edge_defaults: ["blocks", "related_to"]
|
||||
detection_keywords: ["blockieren", "fürchten", "vermeiden", "hindern", "zweifeln"]
|
||||
schema:
|
||||
- "Beschreibung der Hürde"
|
||||
|
|
@ -171,6 +194,7 @@ types:
|
|||
belief:
|
||||
chunking_profile: sliding_short
|
||||
retriever_weight: 0.90
|
||||
edge_defaults: ["related_to"]
|
||||
detection_keywords: ["glauben", "meinen", "annehmen", "überzeugen"]
|
||||
schema:
|
||||
- "Der Glaubenssatz"
|
||||
|
|
@ -179,15 +203,18 @@ types:
|
|||
profile:
|
||||
chunking_profile: structured_smart_edges_strict
|
||||
retriever_weight: 0.70
|
||||
edge_defaults: ["references", "related_to"]
|
||||
detection_keywords: ["verkörpern", "verantworten", "agieren", "repräsentieren"]
|
||||
schema:
|
||||
- "Rolle / Identität"
|
||||
- "Fakten & Daten"
|
||||
- "Historie"
|
||||
|
||||
|
||||
idea:
|
||||
chunking_profile: sliding_short
|
||||
retriever_weight: 0.70
|
||||
edge_defaults: ["leads_to", "references"]
|
||||
detection_keywords: ["einfall", "gedanke", "potenzial", "möglichkeit"]
|
||||
schema:
|
||||
- "Der Kerngedanke"
|
||||
|
|
@ -197,6 +224,7 @@ types:
|
|||
skill:
|
||||
chunking_profile: sliding_smart_edges
|
||||
retriever_weight: 0.90
|
||||
edge_defaults: ["references", "related_to"]
|
||||
detection_keywords: ["lernen", "beherrschen", "üben", "fertigkeit", "kompetenz"]
|
||||
schema:
|
||||
- "Definition der Fähigkeit"
|
||||
|
|
@ -206,6 +234,7 @@ types:
|
|||
habit:
|
||||
chunking_profile: sliding_short
|
||||
retriever_weight: 0.85
|
||||
edge_defaults: ["related_to", "triggered_by"]
|
||||
detection_keywords: ["gewohnheit", "routine", "automatismus", "immer wenn"]
|
||||
schema:
|
||||
- "Auslöser (Trigger)"
|
||||
|
|
@ -214,8 +243,9 @@ types:
|
|||
- "Strategie"
|
||||
|
||||
need:
|
||||
chunking_profile: structured_smart_edges
|
||||
chunking_profile: sliding_smart_edges
|
||||
retriever_weight: 1.05
|
||||
edge_defaults: ["related_to", "impacts"]
|
||||
detection_keywords: ["bedürfnis", "brauchen", "mangel", "erfüllung"]
|
||||
schema:
|
||||
- "Das Bedürfnis"
|
||||
|
|
@ -223,8 +253,9 @@ types:
|
|||
- "Bezug zu Werten"
|
||||
|
||||
motivation:
|
||||
chunking_profile: structured_smart_edges
|
||||
chunking_profile: sliding_smart_edges
|
||||
retriever_weight: 0.95
|
||||
edge_defaults: ["drives", "references"]
|
||||
detection_keywords: ["motivation", "antrieb", "warum", "energie"]
|
||||
schema:
|
||||
- "Der Antrieb"
|
||||
|
|
@ -234,77 +265,86 @@ types:
|
|||
bias:
|
||||
chunking_profile: sliding_short
|
||||
retriever_weight: 0.80
|
||||
edge_defaults: ["affects", "related_to"]
|
||||
detection_keywords: ["denkfehler", "verzerrung", "vorurteil", "falle"]
|
||||
schema: ["Beschreibung der Verzerrung", "Typische Situationen", "Gegenstrategie"]
|
||||
|
||||
state:
|
||||
chunking_profile: sliding_short
|
||||
retriever_weight: 0.60
|
||||
edge_defaults: ["impacts"]
|
||||
detection_keywords: ["stimmung", "energie", "gefühl", "verfassung"]
|
||||
schema: ["Aktueller Zustand", "Auslöser", "Auswirkung auf den Tag"]
|
||||
|
||||
boundary:
|
||||
chunking_profile: structured_smart_edges
|
||||
chunking_profile: sliding_smart_edges
|
||||
retriever_weight: 0.90
|
||||
edge_defaults: ["protects", "related_to"]
|
||||
detection_keywords: ["grenze", "nein sagen", "limit", "schutz"]
|
||||
schema: ["Die Grenze", "Warum sie wichtig ist", "Konsequenz bei Verletzung"]
|
||||
# --- STRATEGIE & RISIKO ---
|
||||
|
||||
goal:
|
||||
chunking_profile: structured_smart_edges
|
||||
chunking_profile: sliding_smart_edges
|
||||
retriever_weight: 0.95
|
||||
detection_keywords: ["ziel", "zielzustand", "kpi", "zeitrahmen", "deadline", "meilenstein"]
|
||||
edge_defaults: ["depends_on", "related_to"]
|
||||
schema: ["Zielzustand", "Zeitrahmen & KPIs", "Motivation"]
|
||||
|
||||
risk:
|
||||
chunking_profile: sliding_short
|
||||
retriever_weight: 0.85
|
||||
edge_defaults: ["related_to", "blocks"]
|
||||
detection_keywords: ["risiko", "gefahr", "bedrohung"]
|
||||
schema: ["Beschreibung des Risikos", "Auswirkungen", "Gegenmaßnahmen"]
|
||||
|
||||
# --- BASIS & WISSEN ---
|
||||
|
||||
concept:
|
||||
chunking_profile: structured_smart_edges
|
||||
retriever_weight: 0.6
|
||||
detection_keywords: ["definition", "konzept", "begriff", "modell", "rahmen", "theorie"]
|
||||
chunking_profile: sliding_smart_edges
|
||||
retriever_weight: 0.60
|
||||
edge_defaults: ["references", "related_to"]
|
||||
schema: ["Definition", "Kontext", "Verwandte Konzepte"]
|
||||
|
||||
task:
|
||||
chunking_profile: sliding_short
|
||||
retriever_weight: 0.8
|
||||
detection_keywords: ["aufgabe", "todo", "next_action", "erledigen", "definition_of_done", "checkliste"]
|
||||
retriever_weight: 0.80
|
||||
edge_defaults: ["depends_on", "part_of"]
|
||||
schema: ["Aufgabe", "Kontext", "Definition of Done"]
|
||||
|
||||
journal:
|
||||
chunking_profile: sliding_standard
|
||||
retriever_weight: 0.8
|
||||
detection_keywords: ["journal", "tagebuch", "log", "eintrag", "reflexion", "heute"]
|
||||
retriever_weight: 0.80
|
||||
edge_defaults: ["references", "related_to"]
|
||||
schema: ["Log-Eintrag", "Gedanken"]
|
||||
|
||||
source:
|
||||
chunking_profile: sliding_standard
|
||||
retriever_weight: 0.5
|
||||
detection_keywords: ["quelle", "paper", "buch", "artikel", "link", "zitat", "studie"]
|
||||
retriever_weight: 0.50
|
||||
edge_defaults: []
|
||||
schema: ["Metadaten", "Zusammenfassung", "Zitate"]
|
||||
|
||||
glossary:
|
||||
chunking_profile: sliding_short
|
||||
retriever_weight: 0.4
|
||||
detection_keywords: ["glossar", "begriff", "definition", "terminologie"]
|
||||
retriever_weight: 0.40
|
||||
edge_defaults: ["related_to"]
|
||||
schema: ["Begriff", "Definition"]
|
||||
|
||||
person:
|
||||
chunking_profile: sliding_standard
|
||||
retriever_weight: 0.5
|
||||
detection_keywords: ["person", "mensch", "kontakt", "name", "beziehung", "stakeholder"]
|
||||
schema: ["Profile", "Beziehung", "Kontext"]
|
||||
retriever_weight: 0.50
|
||||
edge_defaults: ["related_to"]
|
||||
schema: ["Rolle", "Beziehung", "Kontext"]
|
||||
|
||||
event:
|
||||
chunking_profile: sliding_standard
|
||||
retriever_weight: 0.6
|
||||
detection_keywords: ["ereignis", "termin", "datum", "ort", "teilnehmer", "meeting"]
|
||||
retriever_weight: 0.60
|
||||
edge_defaults: ["related_to"]
|
||||
schema: ["Datum & Ort", "Teilnehmer", "Ergebnisse"]
|
||||
|
||||
# --- FALLBACK ---
|
||||
|
||||
default:
|
||||
chunking_profile: sliding_standard
|
||||
retriever_weight: 1.0
|
||||
detection_keywords: []
|
||||
retriever_weight: 1.00
|
||||
edge_defaults: ["references"]
|
||||
schema: ["Inhalt"]
|
||||
|
|
@ -1 +0,0 @@
|
|||
[0114/152756.633:ERROR:third_party\crashpad\crashpad\util\win\registration_protocol_win.cc:108] CreateFile: Das System kann die angegebene Datei nicht finden. (0x2)
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
# Mindnet V3.0: Der Aufstieg des Digitalen Zwillings
|
||||
## Von der Wissensdatenbank zum strategischen Partner – Ein Paradigmenwechsel
|
||||
|
||||
### Einleitung: Die Vision von Version 3.0
|
||||
Mit der Vollendung des Meilensteins WP25 (inklusive der Architektur-Erweiterungen 25a und 25b) transformiert sich Mindnet von einem reinen Retrieval-System (V2) zu einem autonomen, agentischen Ökosystem (V3.0). Mindnet V3.0 ist nicht länger nur ein Werkzeug zur Informationswiedergabe; es ist ein **Digitaler Zwilling**, der in der Lage ist, komplexe Realitäten durch Multi-Stream-Analysen zu erfassen, strategische Empfehlungen auf Basis individueller Werte zu geben und eine bisher unerreichte Ausfallsicherheit zu garantieren.
|
||||
|
||||
---
|
||||
|
||||
### Die 6 Säulen der Mindnet V3.0 Architektur
|
||||
|
||||
#### 1. Agentic Multi-Stream Retrieval (WP-25)
|
||||
Das Herzstück von V3.0 ist die neue `DecisionEngine`. Während herkömmliche Systeme lediglich eine einfache Vektorsuche durchführen, orchestriert die DecisionEngine parallele Wissens-Streams:
|
||||
* **Werte-Stream:** Abgleich von Anfragen mit Ihrer ethischen und strategischen Identität.
|
||||
* **Fakten-Stream:** Analyse der operativen Realität und aktueller Projektdaten.
|
||||
* **Biografie-Stream:** Integration persönlicher Erfahrungen und historischer Kontexte.
|
||||
* **Risiko-Radar:** Proaktive Identifikation von Hindernissen und Zielkonflikten.
|
||||
* **Technik-Wissen:** Tiefgreifende fachliche Expertise für spezialisierte Aufgaben.
|
||||
|
||||
Dieses System erlaubt es Mindnet, eine Anfrage aus fünf verschiedenen Perspektiven gleichzeitig zu beleuchten, bevor eine finale Synthese erfolgt.
|
||||
|
||||
#### 2. Mixture of Experts (MoE) & Dynamic Profiling (WP-25a)
|
||||
Mindnet V3.0 nutzt nicht mehr nur "ein" Modell. Über die zentrale Steuerung in der `llm_profiles.yaml` wird für jede Teilaufgabe der ideale "Experte" gerufen:
|
||||
* **Der Architekt (Gemini 2.0 Flash):** Für hochkomplexe reasoning-intensive Synthesen.
|
||||
* **Der Ingenieur (Qwen 2.5):** Spezialisiert auf präzise Code-Generierung und technische Problemlösung.
|
||||
* **Der Dampfhammer (Mistral 7B):** Optimiert für blitzschnelles Routing und asynchrone Inhaltskompression.
|
||||
* **Der Wächter (Phi-3 Mini):** Ein lokales Modell via Ollama, das maximale Privatsphäre für sensible Identitätsdaten garantiert.
|
||||
|
||||
#### 3. Hierarchische Lazy-Prompt-Orchestration (WP-25b)
|
||||
Ein technologisches Highlight ist die Einführung des **Lazy-Promptings**. Prompts werden nicht mehr statisch im Code verwaltet, sondern erst im Moment der Modellauswahl hierarchisch aufgelöst:
|
||||
1. **Modell-Ebene:** Spezifisch für die jeweilige Modell-ID optimierte Instruktionen.
|
||||
2. **Provider-Ebene:** Fallback-Anweisungen für OpenRouter oder Ollama.
|
||||
3. **Global-Ebene:** Sicherheits-Instruktionen als ultimativer Anker.
|
||||
|
||||
Dies garantiert, dass jedes Modell in seiner "Muttersprache" angesprochen wird, was die Antwortqualität drastisch erhöht.
|
||||
|
||||
#### 4. Die unzerstörbare Fallback-Kaskade
|
||||
Resilienz ist in V3.0 kein Schlagwort, sondern ein Algorithmus. Sollte ein Cloud-Anbieter (wie OpenRouter) ausfallen oder in ein Rate-Limit laufen, reagiert das System autonom:
|
||||
* Automatischer Wechsel auf das Backup-Profil (z.B. von Gemini auf Llama).
|
||||
* In letzter Instanz: Rückzug auf die lokale Hardware (Ollama/Phi-3), sodass Mindnet auch offline voll einsatzfähig bleibt.
|
||||
* **Lazy-Re-Formatting:** Beim Wechsel des Modells wird auch der Prompt sofort neu geladen und für das neue Modell optimiert.
|
||||
|
||||
#### 5. Hochpräzises Intent-Routing mit Regex-Cleaning
|
||||
Durch den neuen ultra-robusten Router in der `DecisionEngine` v1.3.2 erkennt Mindnet Nutzerintentionen mit chirurgischer Präzision. Modell-Artefakte (wie Stop-Marker oder überflüssige Tags freier Modelle) werden durch aggressive Regex-Filter eliminiert, bevor sie das System-Routing stören können. Dies stellt sicher, dass eine Coding-Frage niemals fälschlicherweise im Fakten-Modus landet.
|
||||
|
||||
#### 6. Semantische Ingestion-Validierung v2.14.0
|
||||
Die Qualität des Wissensgraphen wird durch eine neue Validierungsebene geschützt. Während des Imports prüft Mindnet semantisch, ob vorgeschlagene Verknüpfungen (Edges) zwischen Informationen wirklich sinnvoll sind. Dabei unterscheidet das System zwischen temporären Netzwerkfehlern und dauerhaften Logikfehlern, um die Integrität Ihres digitalen Gedächtnisses zu wahren.
|
||||
|
||||
---
|
||||
|
||||
### Technische Highlights für Power-User
|
||||
|
||||
| Feature | Technologie | Nutzen |
|
||||
| :--- | :--- | :--- |
|
||||
| **Orchestrator** | `DecisionEngine v1.3.2` | Agentische Steuerung & Multi-Stream Retrieval |
|
||||
| **Hybrid Cloud** | OpenRouter & Ollama | Maximale Flexibilität zwischen Leistung und Datenschutz |
|
||||
| **Traceability** | `[PROMPT-TRACE]` Logs | Volle Transparenz über die genutzten KI-Instruktionen |
|
||||
| **Context Guard** | Asynchrone Kompression | Optimierung der Kontextfenster für maximale Kosten-Effizienz |
|
||||
| **Resilienz** | Rekursive Fallback-Kaskade | 100% Verfügbarkeit durch Cloud-to-Local Automatisierung |
|
||||
|
||||
---
|
||||
|
||||
### Fazit: Ihr Gehirn, erweitert durch Mindnet V3.0
|
||||
Mindnet V3.0 ist das Ergebnis einer konsequenten Weiterentwicklung hin zu einer **Zero-Failure-Architektur**. Durch die Kombination aus agentischer Intelligenz, hybrider Modellnutzung und der neuen Lazy-Prompt-Infrastruktur bietet es eine Basis, die nicht nur mit Ihrem Wissen wächst, sondern aktiv dabei hilft, dieses Wissen in strategisches Handeln zu übersetzen.
|
||||
|
||||
**Willkommen in der Ära von Mindnet V3.0 – Ihr strategischer Partner ist bereit.**
|
||||
|
||||
---
|
||||
*Dokumentations-Identifikator: `mindnet_v3_core_release`*
|
||||
*Synchronisations-Stand: WP-25b Final*
|
||||
|
|
@ -18,12 +18,9 @@ Das Repository ist in **logische Domänen** unterteilt.
|
|||
*Zielgruppe: Alle*
|
||||
| Datei | Inhalt & Zweck |
|
||||
| :--- | :--- |
|
||||
| `README.md` | **Einstiegspunkt.** Übersicht über die Dokumentationsstruktur, Schnellzugriff nach Rollen und Navigation. |
|
||||
| `00_quickstart.md` | **Schnellstart.** Installation und erste Schritte in 15 Minuten. Ideal für neue Benutzer. |
|
||||
| `00_vision_and_strategy.md` | **Strategie.** Warum bauen wir das? Prinzipien (Privacy, Local-First), High-Level Architektur. |
|
||||
| `00_glossary.md` | **Definitionen.** Was bedeutet "Smart Edge", "Traffic Control", "Chunk"? Verhindert Begriffsverwirrung. |
|
||||
| `00_documentation_map.md` | **Dieser Index.** Navigationshilfe. |
|
||||
| `00_quality_checklist.md` | **Qualitätsprüfung.** Systematische Checkliste zur Vollständigkeitsprüfung für alle Rollen. |
|
||||
|
||||
### 📂 01_User_Manual (Anwendung)
|
||||
*Zielgruppe: Mindmaster, Autoren, Power-User*
|
||||
|
|
@ -31,8 +28,7 @@ Das Repository ist in **logische Domänen** unterteilt.
|
|||
| :--- | :--- |
|
||||
| `01_chat_usage_guide.md` | **Bedienung.** Wie steuere ich die Personas (Berater, Spiegel)? Wie nutze ich das Feedback? |
|
||||
| `01_knowledge_design.md` | **Content-Regeln.** Die "Bibel" für den Vault. Erklärt Note-Typen, Matrix-Logik und Markdown-Syntax. |
|
||||
| `01_authoring_guidelines.md` | **Content strukturieren.** Primäres Werkzeug, um Wissen so zu strukturieren, dass Mindnet die Persönlichkeit spiegelt, empathisch reagiert und strategisch berät. |
|
||||
| `01_obsidian_integration_guide.md` | **Obsidian Setup.** Technische Anleitung für die Integration von Mindnet mit Obsidian (Templater, Skripte, Workflows). |
|
||||
| `01-authoring-guidelines` | **Content strukturieren-.** primäres Werkzeug, um Wissen so zu strukturieren, so dass Mindnet die Persönlichkeit spiegelt, empathisch reagiert und strategisch berät |
|
||||
|
||||
### 📂 02_Concepts (Fachliche Logik)
|
||||
*Zielgruppe: Architekten, Product Owner*
|
||||
|
|
@ -40,7 +36,6 @@ Das Repository ist in **logische Domänen** unterteilt.
|
|||
| :--- | :--- |
|
||||
| `02_concept_graph_logic.md` | **Graph-Theorie.** Abstrakte Erklärung von Knoten, Kanten, Provenance und Idempotenz. |
|
||||
| `02_concept_ai_personality.md`| **KI-Verhalten.** Konzepte hinter dem Hybrid Router, Empathie-Modell und "Teach-the-AI". |
|
||||
| `02_concept_architecture_patterns.md` | **Architektur-Patterns.** Design-Entscheidungen, modulare Struktur (WP-14), Resilienz-Patterns und Erweiterbarkeit. |
|
||||
|
||||
### 📂 03_Technical_Reference (Technik & Code)
|
||||
*Zielgruppe: Entwickler, DevOps. (Enthält JSON/YAML Beispiele)*
|
||||
|
|
@ -51,16 +46,13 @@ Das Repository ist in **logische Domänen** unterteilt.
|
|||
| `03_tech_retrieval_scoring.md` | **Suche.** Die mathematischen Formeln für Scoring, Hybrid Search und Explanation Layer. |
|
||||
| `03_tech_chat_backend.md` | **API & LLM.** Implementation des Routers, Traffic Control (Semaphore) und Feedback-Traceability. |
|
||||
| `03_tech_frontend.md` | **UI & Graph.** Architektur des Streamlit-Frontends, State-Management, Cytoscape-Integration und Editor-Logik. |
|
||||
| `03_tech_configuration.md` | **Config.** Referenztabellen für `.env`, `types.yaml`, `decision_engine.yaml`, `llm_profiles.yaml`, `prompts.yaml`. **Neu:** Verbindungen zwischen Config-Dateien, Praxisbeispiel und Mermaid-Grafik. |
|
||||
| `03_tech_api_reference.md` | **API-Referenz.** Vollständige Dokumentation aller Endpunkte (`/query`, `/chat`, `/ingest`, `/graph`, etc.). |
|
||||
| `03_tech_configuration.md` | **Config.** Referenztabellen für `.env`, `types.yaml` und `retriever.yaml`. |
|
||||
|
||||
### 📂 04_Operations (Betrieb)
|
||||
*Zielgruppe: Administratoren*
|
||||
| Datei | Inhalt & Zweck |
|
||||
| :--- | :--- |
|
||||
| `04_admin_operations.md` | **Runbook.** Installation, Docker-Setup, Backup/Restore, Troubleshooting Guide. |
|
||||
| `04_server_operation_manual.md` | **Server-Betrieb.** Detaillierte Dokumentation für den Betrieb auf llm-node (Systemd, Borgmatic, Disaster Recovery). |
|
||||
| `04_deployment_guide.md` | **Deployment.** CI/CD-Pipelines, Rollout-Strategien, Versionierung, Rollback und Pre/Post-Deployment-Checklisten. |
|
||||
|
||||
### 📂 05_Development (Code)
|
||||
*Zielgruppe: Entwickler*
|
||||
|
|
@ -68,7 +60,6 @@ Das Repository ist in **logische Domänen** unterteilt.
|
|||
| :--- | :--- |
|
||||
| `05_developer_guide.md` | **Workflow.** Hardware-Setup (Win/Pi/Beelink), Git-Flow, Test-Befehle, Modul-Interna. |
|
||||
| `05_genai_best_practices.md` | **AI Workflow.** Prompt-Library, Templates und Best Practices für die Entwicklung mit LLMs. |
|
||||
| `05_testing_guide.md` | **Testing.** Umfassender Test-Guide: Strategien, Frameworks, Test-Daten, Best Practices. |
|
||||
|
||||
### 📂 06_Roadmap & 99_Archive
|
||||
*Zielgruppe: Projektleitung*
|
||||
|
|
@ -91,9 +82,7 @@ Nutze diese Matrix, wenn du ein Workpackage bearbeitest, um die Dokumentation ko
|
|||
| **Retrieval / Scoring** | `03_tech_retrieval_scoring.md` (Formeln anpassen) |
|
||||
| **Frontend / Visualisierung** | 1. `03_tech_frontend.md` (Technische Details)<br>2. `01_chat_usage_guide.md` (Bedienung) |
|
||||
| **Chat-Logik / Prompts**| 1. `02_concept_ai_personality.md` (Konzept)<br>2. `03_tech_chat_backend.md` (Tech)<br>3. `01_chat_usage_guide.md` (User-Sicht) |
|
||||
| **Architektur / Design-Patterns** | 1. `02_concept_architecture_patterns.md` (Patterns & Entscheidungen)<br>2. `02_concept_graph_logic.md` (Graph-Theorie)<br>3. `05_developer_guide.md` (Modulare Struktur) |
|
||||
| **Deployment / Server** | 1. `04_deployment_guide.md` (CI/CD, Rollout)<br>2. `04_admin_operations.md` (Installation, Wartung)<br>3. `04_server_operation_manual.md` (Server-Betrieb) |
|
||||
| **Testing / QA** | 1. `05_testing_guide.md` (Test-Strategien & Frameworks)<br>2. `05_developer_guide.md` (Test-Befehle) |
|
||||
| **Deployment / Server** | `04_admin_operations.md` |
|
||||
| **Neuen Features (Allg.)**| `06_active_roadmap.md` (Status Update) |
|
||||
|
||||
---
|
||||
|
|
@ -122,37 +111,3 @@ Damit dieses System wartbar bleibt (auch für KI-Agenten wie NotebookLM), gelten
|
|||
context: "Beschreibung der Scoring-Formel."
|
||||
---
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Schnellzugriff & Empfehlungen
|
||||
|
||||
### Für neue Benutzer
|
||||
1. Starte mit **[Schnellstart](00_quickstart.md)** für die Installation
|
||||
2. Lese **[Vision & Strategie](00_vision_and_strategy.md)** für das große Bild
|
||||
3. Nutze **[Chat Usage Guide](../01_User_Manual/01_chat_usage_guide.md)** für die ersten Schritte
|
||||
|
||||
### Für Entwickler
|
||||
1. **[Developer Guide](../05_Development/05_developer_guide.md)** - Umfassender technischer Guide
|
||||
2. **[Technical References](../03_Technical_References/)** - Detaillierte API- und Architektur-Dokumentation
|
||||
3. **[GenAI Best Practices](../05_Development/05_genai_best_practices.md)** - Workflow mit LLMs
|
||||
|
||||
### Für Administratoren
|
||||
1. **[Admin Operations](../04_Operations/04_admin_operations.md)** - Installation und Wartung
|
||||
2. **[Server Operations Manual](../04_Operations/04_server_operation_manual.md)** - Server-Betrieb und Disaster Recovery
|
||||
3. **[Troubleshooting Guide](../04_Operations/04_admin_operations.md#33-troubleshooting-guide)** - Häufige Probleme und Lösungen
|
||||
|
||||
### Für Autoren
|
||||
1. **[Knowledge Design](../01_User_Manual/01_knowledge_design.md)** - Content-Regeln und Best Practices
|
||||
2. **[Authoring Guidelines](../01_User_Manual/01_authoring_guidelines.md)** - Strukturierung für den Digitalen Zwilling
|
||||
3. **[Obsidian Integration](../01_User_Manual/01_obsidian_integration_guide.md)** - Workflow-Optimierung
|
||||
|
||||
---
|
||||
|
||||
## 6. Dokumentations-Status
|
||||
|
||||
**Aktuelle Version:** 3.1.1
|
||||
**Letzte Aktualisierung:** 2026-01-02
|
||||
**Status:** ✅ Vollständig und aktiv gepflegt
|
||||
|
||||
**Hinweis:** Diese Dokumentation wird kontinuierlich aktualisiert. Bei Fragen oder Verbesserungsvorschlägen bitte im Repository melden.
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
doc_type: glossary
|
||||
audience: all
|
||||
status: active
|
||||
version: 4.5.8
|
||||
context: "Zentrales Glossar für Mindnet v4.5.8. Enthält Definitionen zu Hybrid-Cloud Resilienz, WP-14 Modularisierung, WP-15b Two-Pass Ingestion, WP-15c Multigraph-Support, WP-25 Agentic Multi-Stream RAG, WP-25a Mixture of Experts (MoE), WP-25b Lazy-Prompt-Orchestration, WP-24c Phase 3 Agentic Edge Validation und Mistral-safe Parsing."
|
||||
version: 2.8.1
|
||||
context: "Zentrales Glossar für Mindnet v2.8. Enthält Definitionen zu Hybrid-Cloud Resilienz, WP-14 Modularisierung, WP-15b Two-Pass Ingestion und Mistral-safe Parsing."
|
||||
---
|
||||
|
||||
# Mindnet Glossar
|
||||
|
|
@ -14,7 +14,7 @@ context: "Zentrales Glossar für Mindnet v4.5.8. Enthält Definitionen zu Hybrid
|
|||
|
||||
* **Note:** Repräsentiert eine Markdown-Datei. Die fachliche Haupteinheit. Verfügt über einen **Status** (stable, draft, system), der das Scoring beeinflusst.
|
||||
* **Chunk:** Ein Textabschnitt einer Note. Die technische Sucheinheit (Vektor).
|
||||
* **Edge:** Eine gerichtete Verbindung zwischen zwei Knoten. Wird in WP-22 durch die Registry validiert. Seit v2.9.1 unterstützt Edges **Section-basierte Links** (`target_section`), sodass mehrere Kanten zwischen denselben Knoten existieren können, wenn sie auf verschiedene Abschnitte zeigen.
|
||||
* **Edge:** Eine gerichtete Verbindung zwischen zwei Knoten. Wird in WP-22 durch die Registry validiert.
|
||||
* **Vault:** Der lokale Ordner mit den Markdown-Dateien (Source of Truth).
|
||||
* **Frontmatter:** Der YAML-Header am Anfang einer Notiz (enthält `id`, `type`, `title`, `status`).
|
||||
|
||||
|
|
@ -23,11 +23,7 @@ context: "Zentrales Glossar für Mindnet v4.5.8. Enthält Definitionen zu Hybrid
|
|||
* **Edge Registry:** Der zentrale Dienst (SSOT), der Kanten-Typen validiert und Aliase in kanonische Typen auflöst. Nutzt `01_edge_vocabulary.md` als Basis.
|
||||
* **LLM Service:** Der Hybrid-Client (v3.3.6), der Anfragen zwischen OpenRouter, Google Gemini und lokalem Ollama routet. Verwaltet Cloud-Timeouts und Quoten-Management. Nutzt zur Text-Bereinigung nun die neutrale `registry.py`, um Circular Imports zu vermeiden.
|
||||
* **Retriever:** Besteht in v2.7+ aus der Orchestrierung (`retriever.py`) und der mathematischen Scoring-Engine (`retriever_scoring.py`). Seit WP-14 im Paket `app.core.retrieval` gekapselt.
|
||||
* **Decision Engine (WP-25):** Der zentrale **Agentic Orchestrator**, der Intents erkennt, parallele Wissens-Streams orchestriert und die Ergebnisse synthetisiert. Implementiert Multi-Stream Retrieval und Intent-basiertes Routing.
|
||||
* **Agentic Multi-Stream RAG (WP-25):** Architektur-Paradigma, bei dem Nutzeranfragen in parallele, spezialisierte Wissens-Streams aufgeteilt werden (Values, Facts, Biography, Risk, Tech), die gleichzeitig abgefragt und zu einer kontextreichen Antwort synthetisiert werden.
|
||||
* **Stream-Tracing (WP-25):** Kennzeichnung jedes Treffers mit seinem Ursprungs-Stream (`stream_origin`), um Feedback-Optimierung pro Wissensbereich zu ermöglichen.
|
||||
* **Intent-basiertes Routing (WP-25):** Hybrid-Modus zur Intent-Erkennung mit Keyword Fast-Path (sofortige Erkennung von Triggern) und LLM Slow-Path (semantische Analyse für unklare Anfragen).
|
||||
* **Wissens-Synthese (WP-25):** Template-basierte Zusammenführung der Ergebnisse aus parallelen Streams mit expliziten Stream-Variablen (z.B. `{values_stream}`, `{risk_stream}`), um dem LLM eine differenzierte Abwägung zu ermöglichen.
|
||||
* **Decision Engine:** Teil des Routers, der Intents erkennt und entsprechende **Boost-Faktoren** für das Retrieval injiziert.
|
||||
* **Traffic Control:** Verwaltet Prioritäten und drosselt Hintergrund-Tasks (z.B. Smart Edges) mittels Semaphoren und Timeouts (45s) zur Vermeidung von System-Hangs.
|
||||
* **Unknown Edges Log:** Die Datei `unknown_edges.jsonl`, in der das System Kanten-Typen protokolliert, die nicht im Dictionary gefunden wurden.
|
||||
* **Database Package (WP-14):** Zentralisiertes Infrastruktur-Paket (`app.core.database`), das den Qdrant-Client (`qdrant.py`) und das Point-Mapping (`qdrant_points.py`) verwaltet.
|
||||
|
|
@ -52,23 +48,3 @@ context: "Zentrales Glossar für Mindnet v4.5.8. Enthält Definitionen zu Hybrid
|
|||
* **Pass 1 (Pre-Scan):** Schnelles Scannen aller Dateien zur Befüllung des LocalBatchCache.
|
||||
* **Pass 2 (Semantic Processing):** Tiefenverarbeitung (Chunking, Embedding, Validierung) nur für geänderte Dateien.
|
||||
* **Circular Import Registry (WP-14):** Entkopplung von Kern-Logik (wie Textbereinigung) in eine neutrale `registry.py`, um Abhängigkeitsschleifen zwischen Diensten und Ingestion-Utilities zu verhindern.
|
||||
* **Deep-Link / Section-basierter Link:** Ein Link wie `[[Note#Section]]`, der auf einen spezifischen Abschnitt innerhalb einer Note verweist. Seit v2.9.1 wird dieser in `target_id="Note"` und `target_section="Section"` aufgeteilt, um "Phantom-Knoten" zu vermeiden und Multigraph-Support zu ermöglichen.
|
||||
* **Atomic Section Logic (v3.9.9):** Chunking-Verfahren, das Sektions-Überschriften und deren Inhalte atomar in Chunks hält (Pack-and-Carry-Over). Verhindert, dass Überschriften über Chunk-Grenzen hinweg getrennt werden.
|
||||
* **Registry-First Profiling (v2.13.12):** Hierarchische Auflösung des Chunking-Profils: Frontmatter > types.yaml Typ-Config > Global Defaults. Stellt sicher, dass Note-Typen automatisch das korrekte Profil erhalten.
|
||||
* **Mixture of Experts (MoE) - WP-25a:** Profilbasierte Experten-Architektur, bei der jede Systemaufgabe (Synthese, Ingestion-Validierung, Routing, Kompression) einem dedizierten Profil zugewiesen wird, das Modell, Provider und Parameter unabhängig von der globalen Konfiguration definiert.
|
||||
* **LLM-Profil:** Zentrale Definition in `llm_profiles.yaml`, die Provider, Modell, Temperature und Fallback-Profil für eine spezifische Aufgabe festlegt (z.B. `synthesis_pro`, `tech_expert`, `ingest_validator`).
|
||||
* **Fallback-Kaskade (WP-25a):** Rekursive Fallback-Logik, bei der bei Fehlern automatisch auf das `fallback_profile` umgeschaltet wird, bis der terminale Endpunkt (`identity_safe`) erreicht wird. Schutz gegen Zirkel-Referenzen via `visited_profiles`-Tracking.
|
||||
* **Pre-Synthesis Kompression (WP-25a):** Asynchrone Verdichtung überlanger Wissens-Streams vor der Synthese, um Token-Verbrauch zu reduzieren und die Synthese zu beschleunigen. Nutzt `compression_profile` (z.B. `compression_fast`).
|
||||
* **Profilgesteuerte Validierung (WP-25a):** Semantische Kanten-Validierung in der Ingestion erfolgt zwingend über das MoE-Profil `ingest_validator` (Temperature 0.0 für Determinismus), unabhängig von der globalen Provider-Konfiguration.
|
||||
* **Lazy-Prompt-Orchestration (WP-25b):** Hierarchisches Prompt-Resolution-System, das Prompts erst im Moment des Modellaustauschs lädt, basierend auf dem exakt aktiven Modell. Ermöglicht modell-spezifisches Tuning und maximale Resilienz bei Modell-Fallbacks.
|
||||
* **Hierarchische Prompt-Resolution (WP-25b):** Dreistufige Auflösungs-Logik: Level 1 (Modell-ID) → Level 2 (Provider) → Level 3 (Default). Gewährleistet, dass jedes Modell das optimale Template erhält.
|
||||
* **PROMPT-TRACE (WP-25b):** Logging-Mechanismus, der die genutzte Prompt-Auflösungs-Ebene protokolliert (`🎯 Level 1`, `📡 Level 2`, `⚓ Level 3`). Bietet vollständige Transparenz über die genutzten Instruktionen.
|
||||
* **Ultra-robustes Intent-Parsing (WP-25b):** Regex-basierter Intent-Parser in der DecisionEngine, der Modell-Artefakte wie `[/S]`, `</s>` oder Newlines zuverlässig bereinigt, um präzises Strategie-Routing zu gewährleisten.
|
||||
* **Differenzierte Ingestion-Validierung (WP-25b):** Unterscheidung zwischen transienten Fehlern (Netzwerk, Timeout) und permanenten Fehlern (Config, Validation). Transiente Fehler erlauben die Kante (Datenverlust vermeiden), permanente Fehler lehnen sie ab (Graph-Qualität schützen).
|
||||
* **Phase 3 Agentic Edge Validation (WP-24c v4.5.8):** Finales Validierungs-Gate für alle Kanten mit `candidate:` Präfix. Nutzt LLM-basierte semantische Prüfung zur Verifizierung von Wissensverknüpfungen. Verhindert "Geister-Verknüpfungen" und sichert die Graph-Qualität gegen Fehlinterpretationen ab.
|
||||
* **candidate: Präfix (WP-24c v4.5.8):** Markierung für unbestätigte Kanten in `rule_id` oder `provenance`. Alle Kanten mit diesem Präfix werden in Phase 3 dem LLM-Validator vorgelegt. Nach erfolgreicher Validierung wird das Präfix entfernt.
|
||||
* **verified Status (WP-24c v4.5.8):** Impliziter Status für Kanten nach erfolgreicher Phase 3 Validierung. Kanten ohne `candidate:` Präfix gelten als verifiziert und werden in die Datenbank geschrieben.
|
||||
* **Note-Scope (WP-24c v4.2.0):** Globale Verbindungen, die der gesamten Note zugeordnet werden (nicht nur einem spezifischen Chunk). Wird durch spezielle Header-Zonen (z.B. `## Smart Edges`) definiert. In Phase 3 Validierung wird `note_summary` oder `note_text` als Kontext verwendet.
|
||||
* **Chunk-Scope (WP-24c v4.2.0):** Lokale Verbindungen, die einem spezifischen Textabschnitt (Chunk) zugeordnet werden. In Phase 3 Validierung wird der spezifische Chunk-Text als Kontext verwendet, falls verfügbar.
|
||||
* **Kontext-Optimierung (WP-24c v4.5.8):** Dynamische Kontext-Auswahl in Phase 3 Validierung basierend auf `scope`. Note-Scope nutzt aggregierten Note-Text, Chunk-Scope nutzt spezifischen Chunk-Text. Optimiert die Validierungs-Genauigkeit durch passenden Kontext.
|
||||
* **rejected_edges (WP-24c v4.5.8):** Liste von Kanten, die in Phase 3 Validierung abgelehnt wurden. Diese Kanten werden **nicht** in die Datenbank geschrieben und vollständig ignoriert. Verhindert persistente "Geister-Verknüpfungen" im Wissensgraphen.
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
---
|
||||
doc_type: quality_assurance
|
||||
audience: all
|
||||
status: active
|
||||
version: 4.5.8
|
||||
context: "Qualitätsprüfung der Dokumentation für alle Rollen: Vollständigkeit, Korrektheit und Anwendbarkeit. Inkludiert WP-24c Phase 3 Agentic Edge Validation, automatische Spiegelkanten und Note-Scope Zonen."
|
||||
---
|
||||
|
||||
# Dokumentations-Qualitätsprüfung
|
||||
|
||||
Diese Checkliste dient zur systematischen Prüfung, ob die Dokumentation alle Fragen jeder Rolle vollständig beantwortet.
|
||||
|
||||
## ✅ Entwickler
|
||||
|
||||
### Setup & Installation
|
||||
- [x] **Lokales Setup:** [Developer Guide](../05_Development/05_developer_guide.md#6-lokales-setup-development)
|
||||
- [x] **Schnellstart:** [Quickstart](00_quickstart.md)
|
||||
- [x] **Hardware-Anforderungen:** [Admin Operations](../04_Operations/04_admin_operations.md#11-voraussetzungen)
|
||||
|
||||
### Architektur & Code
|
||||
- [x] **Modulare Struktur:** [Developer Guide - Architektur](../05_Development/05_developer_guide.md#4-projektstruktur--modul-referenz-deep-dive)
|
||||
- [x] **Design-Patterns:** [Architektur-Patterns](../02_concepts/02_concept_architecture_patterns.md)
|
||||
- [x] **API-Referenz:** [API Reference](../03_Technical_References/03_tech_api_reference.md)
|
||||
- [x] **Datenmodell:** [Data Model](../03_Technical_References/03_tech_data_model.md)
|
||||
|
||||
### Entwicklung & Erweiterung
|
||||
- [x] **Workflow:** [Developer Guide - Workflow](../05_Development/05_developer_guide.md#7-der-entwicklungs-zyklus-workflow)
|
||||
- [x] **Erweiterungs-Guide:** [Teach-the-AI](../05_Development/05_developer_guide.md#8-erweiterungs-guide-teach-the-ai)
|
||||
- [x] **GenAI Best Practices:** [GenAI Best Practices](../05_Development/05_genai_best_practices.md)
|
||||
|
||||
### Testing
|
||||
- [x] **Test-Strategien:** [Testing Guide](../05_Development/05_testing_guide.md)
|
||||
- [x] **Test-Frameworks:** [Testing Guide - Frameworks](../05_Development/05_testing_guide.md#3-test-frameworks--tools)
|
||||
- [x] **Test-Daten:** [Testing Guide - Test-Daten](../05_Development/05_testing_guide.md#2-test-daten--vaults)
|
||||
|
||||
### Debugging & Troubleshooting
|
||||
- [x] **Troubleshooting:** [Developer Guide - Troubleshooting](../05_Development/05_developer_guide.md#10-troubleshooting--one-liners)
|
||||
- [x] **Debug-Tools:** [Testing Guide - Debugging](../05_Development/05_testing_guide.md#7-debugging--diagnose)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Administratoren
|
||||
|
||||
### Installation & Setup
|
||||
- [x] **Installation:** [Admin Operations](../04_Operations/04_admin_operations.md#1-installation--setup)
|
||||
- [x] **Docker Setup:** [Admin Operations - Qdrant](../04_Operations/04_admin_operations.md#12-qdrant-docker)
|
||||
- [x] **Systemd Services:** [Admin Operations - Deployment](../04_Operations/04_admin_operations.md#2-deployment-systemd-services)
|
||||
|
||||
### Betrieb & Wartung
|
||||
- [x] **Monitoring:** [Admin Operations - Wartung](../04_Operations/04_admin_operations.md#3-wartung--monitoring)
|
||||
- [x] **Backup & Restore:** [Admin Operations - Backup](../04_Operations/04_admin_operations.md#4-backup--restore)
|
||||
- [x] **Troubleshooting:** [Admin Operations - Troubleshooting](../04_Operations/04_admin_operations.md#33-troubleshooting-guide)
|
||||
|
||||
### Server-Betrieb
|
||||
- [x] **Server-Konfiguration:** [Server Operations Manual](../04_Operations/04_server_operation_manual.md)
|
||||
- [x] **Disaster Recovery:** [Server Operations - DR](../04_Operations/04_server_operation_manual.md#5-disaster-recovery-wiederherstellung-two-stage-dr)
|
||||
- [x] **Backup-Strategie:** [Server Operations - Backup](../04_Operations/04_server_operation_manual.md#4-backup-strategie-borgmatic)
|
||||
|
||||
### Konfiguration
|
||||
- [x] **ENV-Variablen:** [Configuration Reference](../03_Technical_References/03_tech_configuration.md#1-environment-variablen-env)
|
||||
- [x] **YAML-Configs:** [Configuration Reference - YAML](../03_Technical_References/03_tech_configuration.md#2-typ-registry-typesyaml)
|
||||
- [x] **Phase 3 Validierung:** [Configuration Reference - ENV](../03_Technical_References/03_tech_configuration.md#1-environment-variablen-env) (MINDNET_LLM_VALIDATION_HEADERS, MINDNET_NOTE_SCOPE_ZONE_HEADERS)
|
||||
- [x] **LLM-Profile:** [Configuration Reference - LLM Profiles](../03_Technical_References/03_tech_configuration.md#6-llm-profile-registry-llm_profilesyaml-v130)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Anwender
|
||||
|
||||
### Erste Schritte
|
||||
- [x] **Schnellstart:** [Quickstart](00_quickstart.md)
|
||||
- [x] **Was ist Mindnet:** [Vision & Strategie](00_vision_and_strategy.md)
|
||||
- [x] **Grundlagen:** [Glossar](00_glossary.md)
|
||||
|
||||
### Nutzung
|
||||
- [x] **Chat-Bedienung:** [Chat Usage Guide](../01_User_Manual/01_chat_usage_guide.md)
|
||||
- [x] **Graph Explorer:** [Chat Usage Guide - Graph](../01_User_Manual/01_chat_usage_guide.md#22-modus--graph-explorer-cytoscape)
|
||||
- [x] **Editor:** [Chat Usage Guide - Editor](../01_User_Manual/01_chat_usage_guide.md#23-modus--manueller-editor)
|
||||
|
||||
### Content-Erstellung
|
||||
- [x] **Knowledge Design:** [Knowledge Design Manual](../01_User_Manual/01_knowledge_design.md)
|
||||
- [x] **Authoring Guidelines:** [Authoring Guidelines](../01_User_Manual/01_authoring_guidelines.md)
|
||||
- [x] **Obsidian-Integration:** [Obsidian Integration](../01_User_Manual/01_obsidian_integration_guide.md)
|
||||
- [x] **Note-Scope Zonen:** [Note-Scope Zonen](../01_User_Manual/NOTE_SCOPE_ZONEN.md) (WP-24c v4.2.0)
|
||||
- [x] **LLM-Validierung:** [LLM-Validierung von Links](../01_User_Manual/LLM_VALIDIERUNG_VON_LINKS.md) (WP-24c v4.5.8)
|
||||
|
||||
### Häufige Fragen
|
||||
- [x] **Wie strukturiere ich Notizen?** → [Knowledge Design](../01_User_Manual/01_knowledge_design.md)
|
||||
- [x] **Welche Note-Typen gibt es?** → [Knowledge Design - Typ-Referenz](../01_User_Manual/01_knowledge_design.md#31-typ-referenz--stream-logik)
|
||||
- [x] **Wie verknüpfe ich Notizen?** → [Knowledge Design - Edges](../01_User_Manual/01_knowledge_design.md#4-edges--verlinkung)
|
||||
- [x] **Wie nutze ich den Chat?** → [Chat Usage Guide](../01_User_Manual/01_chat_usage_guide.md)
|
||||
- [x] **Was sind automatische Spiegelkanten?** → [Knowledge Design - Spiegelkanten](../01_User_Manual/01_knowledge_design.md#43-automatische-spiegelkanten-invers-logik---wp-24c-v458)
|
||||
- [x] **Was ist Phase 3 Validierung?** → [Knowledge Design - Phase 3](../01_User_Manual/01_knowledge_design.md#44-explizite-vs-validierte-kanten-phase-3-validierung---wp-24c-v458)
|
||||
- [x] **Was sind Note-Scope Zonen?** → [Note-Scope Zonen](../01_User_Manual/NOTE_SCOPE_ZONEN.md)
|
||||
- [x] **Wann nutze ich explizite vs. validierte Links?** → [Knowledge Design - Explizite vs. Validierte](../01_User_Manual/01_knowledge_design.md#44-explizite-vs-validierte-kanten-phase-3-validierung---wp-24c-v458)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Tester
|
||||
|
||||
### Test-Strategien
|
||||
- [x] **Test-Pyramide:** [Testing Guide - Strategien](../05_Development/05_testing_guide.md#1-test-strategie--ebenen)
|
||||
- [x] **Unit Tests:** [Testing Guide - Unit Tests](../05_Development/05_testing_guide.md#11-unit-tests-pytest)
|
||||
- [x] **Integration Tests:** [Testing Guide - Integration](../05_Development/05_testing_guide.md#12-integration-tests)
|
||||
- [x] **E2E Tests:** [Testing Guide - E2E](../05_Development/05_testing_guide.md#13-e2e--smoke-tests)
|
||||
|
||||
### Test-Frameworks
|
||||
- [x] **Pytest:** [Testing Guide - Frameworks](../05_Development/05_testing_guide.md#31-pytest-unit-tests)
|
||||
- [x] **Unittest:** [Testing Guide - Unittest](../05_Development/05_testing_guide.md#32-unittest-e2e-tests)
|
||||
- [x] **Shell-Skripte:** [Testing Guide - Shell](../05_Development/05_testing_guide.md#33-shell-skripte-e2e-roundtrip)
|
||||
|
||||
### Test-Daten & Tools
|
||||
- [x] **Test-Vault erstellen:** [Testing Guide - Test-Daten](../05_Development/05_testing_guide.md#21-test-vault-erstellen)
|
||||
- [x] **Test-Skripte:** [Developer Guide - Scripts](../05_Development/05_developer_guide.md#44-scripts--tooling-die-admin-toolbox)
|
||||
- [x] **Test-Checkliste:** [Testing Guide - Checkliste](../05_Development/05_testing_guide.md#8-test-checkliste-für-pull-requests)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Deployment
|
||||
|
||||
### Deployment-Prozesse
|
||||
- [x] **Deployment-Guide:** [Deployment Guide](../04_Operations/04_deployment_guide.md)
|
||||
- [x] **CI/CD Pipeline:** [Deployment Guide - CI/CD](../04_Operations/04_deployment_guide.md#9-cicd-pipeline-details)
|
||||
- [x] **Rollout-Strategien:** [Deployment Guide - Rollout](../04_Operations/04_deployment_guide.md#4-rollout-strategien)
|
||||
|
||||
### Versionierung & Releases
|
||||
- [x] **Version-Schema:** [Deployment Guide - Versionierung](../04_Operations/04_deployment_guide.md#51-version-schema)
|
||||
- [x] **Release-Prozess:** [Deployment Guide - Release](../04_Operations/04_deployment_guide.md#52-release-prozess)
|
||||
|
||||
### Rollback & Recovery
|
||||
- [x] **Rollback-Strategien:** [Deployment Guide - Rollback](../04_Operations/04_deployment_guide.md#6-rollback-strategien)
|
||||
- [x] **Disaster Recovery:** [Server Operations - DR](../04_Operations/04_server_operation_manual.md#5-disaster-recovery-wiederherstellung-two-stage-dr)
|
||||
|
||||
### Pre/Post-Deployment
|
||||
- [x] **Pre-Deployment Checkliste:** [Deployment Guide - Checkliste](../04_Operations/04_deployment_guide.md#7-pre-deployment-checkliste)
|
||||
- [x] **Post-Deployment Validierung:** [Deployment Guide - Validierung](../04_Operations/04_deployment_guide.md#8-post-deployment-validierung)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Zusammenfassung
|
||||
|
||||
### Vollständigkeit nach Rolle
|
||||
|
||||
| Rolle | Abgedeckte Themen | Status |
|
||||
| :--- | :--- | :--- |
|
||||
| **Entwickler** | Setup, Architektur, Code, Testing, Debugging | ✅ Vollständig |
|
||||
| **Administratoren** | Installation, Betrieb, Wartung, Backup, DR | ✅ Vollständig |
|
||||
| **Anwender** | Nutzung, Content-Erstellung, Workflows | ✅ Vollständig |
|
||||
| **Tester** | Test-Strategien, Frameworks, Tools | ✅ Vollständig |
|
||||
| **Deployment** | CI/CD, Rollout, Versionierung, Rollback | ✅ Vollständig |
|
||||
|
||||
### Neue Dokumente
|
||||
|
||||
1. ✅ `05_testing_guide.md` - Umfassender Test-Guide
|
||||
2. ✅ `04_deployment_guide.md` - Vollständiger Deployment-Guide
|
||||
3. ✅ `02_concept_architecture_patterns.md` - Architektur-Patterns
|
||||
4. ✅ `03_tech_api_reference.md` - API-Referenz
|
||||
5. ✅ `00_quickstart.md` - Schnellstart-Anleitung
|
||||
6. ✅ `README.md` - Dokumentations-Einstiegspunkt
|
||||
|
||||
### Aktualisierte Dokumente
|
||||
|
||||
1. ✅ `00_documentation_map.md` - Alle neuen Dokumente aufgenommen
|
||||
2. ✅ `04_admin_operations.md` - Troubleshooting erweitert, Phase 3 Validierung dokumentiert
|
||||
3. ✅ `05_developer_guide.md` - Modulare Struktur ergänzt, WP-24c Phase 3 dokumentiert
|
||||
4. ✅ `03_tech_ingestion_pipeline.md` - Background Tasks dokumentiert, Phase 3 Agentic Validation hinzugefügt
|
||||
5. ✅ `03_tech_configuration.md` - Fehlende ENV-Variablen ergänzt, WP-24c Konfiguration dokumentiert
|
||||
6. ✅ `00_vision_and_strategy.md` - Design-Entscheidungen ergänzt
|
||||
7. ✅ `01_knowledge_design.md` - Automatische Spiegelkanten, Phase 3 Validierung, Note-Scope Zonen dokumentiert
|
||||
8. ✅ `02_concept_graph_logic.md` - Phase 3 Validierung, automatische Spiegelkanten, Note-Scope vs. Chunk-Scope dokumentiert
|
||||
9. ✅ `03_tech_data_model.md` - candidate: Präfix, verified Status, virtual Flag dokumentiert
|
||||
10. ✅ `NOTE_SCOPE_ZONEN.md` - Phase 3 Validierung integriert
|
||||
11. ✅ `LLM_VALIDIERUNG_VON_LINKS.md` - Phase 3 statt global_pool, Kontext-Optimierung dokumentiert
|
||||
12. ✅ `05_testing_guide.md` - WP-24c Test-Szenarien hinzugefügt
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Alle Rollen vollständig abgedeckt
|
||||
**Letzte Prüfung:** 2025-01-XX
|
||||
**Version:** 2.9.1
|
||||
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
---
|
||||
doc_type: quickstart_guide
|
||||
audience: user, developer, admin
|
||||
status: active
|
||||
version: 2.9.1
|
||||
context: "Schnellstart-Anleitung für neue Benutzer von Mindnet"
|
||||
---
|
||||
|
||||
# Mindnet Schnellstart
|
||||
|
||||
Diese Anleitung hilft dir, in 15 Minuten mit Mindnet loszulegen.
|
||||
|
||||
## 🎯 Was ist Mindnet?
|
||||
|
||||
Mindnet ist ein **persönliches KI-Gedächtnis**, das:
|
||||
- Dein Wissen in Markdown-Notizen speichert
|
||||
- Semantisch verknüpft (Wissensgraph)
|
||||
- Als intelligenter Dialogpartner agiert (RAG-Chat)
|
||||
- **Lokal und privat** läuft (Privacy First)
|
||||
|
||||
## 📋 Voraussetzungen
|
||||
|
||||
- **Python 3.10+** installiert
|
||||
- **Docker** installiert (für Qdrant)
|
||||
- **Ollama** installiert (für lokale LLMs)
|
||||
- Optional: **Obsidian** (für komfortables Schreiben)
|
||||
|
||||
## ⚡ Installation (5 Minuten)
|
||||
|
||||
### Schritt 1: Repository klonen
|
||||
|
||||
```bash
|
||||
git clone <repository-url> mindnet
|
||||
cd mindnet
|
||||
```
|
||||
|
||||
### Schritt 2: Virtuelle Umgebung erstellen
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
||||
```
|
||||
|
||||
### Schritt 3: Abhängigkeiten installieren
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Schritt 4: Qdrant starten (Docker)
|
||||
|
||||
```bash
|
||||
docker compose up -d qdrant
|
||||
```
|
||||
|
||||
### Schritt 5: Ollama-Modelle laden
|
||||
|
||||
```bash
|
||||
ollama pull phi3:mini
|
||||
ollama pull nomic-embed-text
|
||||
```
|
||||
|
||||
### Schritt 6: Konfiguration anpassen
|
||||
|
||||
Erstelle eine `.env`-Datei im Projektroot:
|
||||
|
||||
```ini
|
||||
QDRANT_URL=http://localhost:6333
|
||||
MINDNET_OLLAMA_URL=http://localhost:11434
|
||||
MINDNET_LLM_MODEL=phi3:mini
|
||||
MINDNET_EMBEDDING_MODEL=nomic-embed-text
|
||||
COLLECTION_PREFIX=mindnet
|
||||
MINDNET_VAULT_ROOT=./vault
|
||||
```
|
||||
|
||||
## 🚀 Erste Schritte (5 Minuten)
|
||||
|
||||
### Schritt 1: Backend starten
|
||||
|
||||
```bash
|
||||
uvicorn app.main:app --reload --port 8001
|
||||
```
|
||||
|
||||
### Schritt 2: Frontend starten (neues Terminal)
|
||||
|
||||
```bash
|
||||
streamlit run app/frontend/ui.py --server.port 8501
|
||||
```
|
||||
|
||||
### Schritt 3: Browser öffnen
|
||||
|
||||
Öffne `http://localhost:8501` im Browser.
|
||||
|
||||
### Schritt 4: Erste Notiz importieren
|
||||
|
||||
Erstelle eine Test-Notiz im `vault/` Ordner:
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: 20250101-test
|
||||
title: Meine erste Notiz
|
||||
type: concept
|
||||
status: active
|
||||
---
|
||||
|
||||
# Meine erste Notiz
|
||||
|
||||
Dies ist eine Test-Notiz für Mindnet.
|
||||
|
||||
[[rel:related_to Mindnet]]
|
||||
```
|
||||
|
||||
Dann importiere sie:
|
||||
|
||||
```bash
|
||||
python3 -m scripts.import_markdown --vault ./vault --prefix mindnet --apply
|
||||
```
|
||||
|
||||
### Schritt 5: Erste Chat-Anfrage
|
||||
|
||||
Im Browser-Chat eingeben:
|
||||
```
|
||||
Was ist Mindnet?
|
||||
```
|
||||
|
||||
## 📚 Nächste Schritte
|
||||
|
||||
Nach dem Schnellstart empfehle ich:
|
||||
|
||||
1. **[Chat Usage Guide](../01_User_Manual/01_chat_usage_guide.md)** - Lerne die Chat-Funktionen kennen
|
||||
2. **[Knowledge Design](../01_User_Manual/01_knowledge_design.md)** - Verstehe, wie du Notizen strukturierst
|
||||
3. **[Authoring Guidelines](../01_User_Manual/01_authoring_guidelines.md)** - Lerne Best Practices für das Schreiben
|
||||
|
||||
## 🆘 Hilfe & Troubleshooting
|
||||
|
||||
**Problem:** Qdrant startet nicht
|
||||
- **Lösung:** Prüfe, ob Docker läuft: `docker ps`
|
||||
|
||||
**Problem:** Ollama-Modell nicht gefunden
|
||||
- **Lösung:** Prüfe mit `ollama list`, ob die Modelle geladen sind
|
||||
|
||||
**Problem:** Import schlägt fehl
|
||||
- **Lösung:** Prüfe die Logs und stelle sicher, dass Qdrant läuft
|
||||
|
||||
Für detaillierte Troubleshooting-Informationen siehe [Admin Operations](../04_Operations/04_admin_operations.md#33-troubleshooting-guide).
|
||||
|
||||
## 🔗 Weitere Ressourcen
|
||||
|
||||
- **[Dokumentationskarte](00_documentation_map.md)** - Übersicht aller Dokumente
|
||||
- **[Glossar](00_glossary.md)** - Wichtige Begriffe erklärt
|
||||
- **[Vision & Strategie](00_vision_and_strategy.md)** - Die Philosophie hinter Mindnet
|
||||
|
||||
---
|
||||
|
||||
**Viel Erfolg mit Mindnet!** 🚀
|
||||
|
||||
|
|
@ -65,41 +65,3 @@ Die folgenden Prinzipien steuern jede technische Entscheidung:
|
|||
|
||||
10. **Local First & Privacy:**
|
||||
Nutzung lokaler LLMs (Ollama) für Inference. Keine Daten verlassen den Server.
|
||||
|
||||
11. **Modulare Architektur (WP-14):**
|
||||
Core-Logik ist in spezialisierte Pakete unterteilt (`chunking/`, `database/`, `graph/`, `ingestion/`, `parser/`, `retrieval/`). Dies ermöglicht unabhängige Entwicklung, Testbarkeit und Wartbarkeit.
|
||||
|
||||
12. **Resilienz durch Kaskaden:**
|
||||
System nutzt Provider-Kaskaden (Cloud → Rate-Limit-Handling → Lokaler Fallback) für hohe Verfügbarkeit. Deep Fallback erkennt auch kognitive Blockaden (Silent Refusals).
|
||||
|
||||
---
|
||||
|
||||
## 4. Design-Entscheidungen & Trade-offs
|
||||
|
||||
### 4.1 Qdrant als Vektor-DB
|
||||
**Entscheidung:** Self-hosted Qdrant statt Managed Service (Pinecone/Weaviate)
|
||||
**Gründe:** Privacy First, Open Source, lokale Kontrolle
|
||||
**Trade-off:** Mehr Wartungsaufwand, aber vollständige Datenhoheit
|
||||
|
||||
### 4.2 Hybrid Retrieval
|
||||
**Entscheidung:** Kombination von Vektor-Suche (Semantik) und Graph-Expansion
|
||||
**Gründe:** Bessere Relevanz durch strukturelle Verbindungen
|
||||
**Trade-off:** Höhere Komplexität, aber deutlich bessere Ergebnisse
|
||||
|
||||
### 4.3 Background Tasks statt Queue-System
|
||||
**Entscheidung:** FastAPI Background Tasks statt Redis/RabbitMQ
|
||||
**Gründe:** Einfacher Setup, ausreichend für Single-User
|
||||
**Trade-off:** Keine Persistenz bei Server-Neustart, aber weniger Infrastruktur
|
||||
|
||||
### 4.4 Markdown als Source of Truth
|
||||
**Entscheidung:** Markdown-Dateien sind primär, Datenbank ist Cache
|
||||
**Gründe:** Versionierbarkeit, Editierbarkeit, Unabhängigkeit
|
||||
**Trade-off:** Zwei Datenquellen, aber maximale Flexibilität
|
||||
|
||||
---
|
||||
|
||||
## 5. Weitere Informationen
|
||||
|
||||
- **Architektur-Patterns:** Siehe [Architektur-Patterns](../02_concepts/02_concept_architecture_patterns.md)
|
||||
- **Graph-Logik:** Siehe [Graph-Logik](../02_concepts/02_concept_graph_logic.md)
|
||||
- **KI-Persönlichkeit:** Siehe [KI-Persönlichkeit](../02_concepts/02_concept_ai_personality.md)
|
||||
|
|
@ -3,7 +3,7 @@ id: 01-authoring-guidelines
|
|||
title: Authoring Guidelines – Handbuch für den Digitalen Zwilling
|
||||
type: principle
|
||||
status: stable
|
||||
version: 1.3.0
|
||||
version: 1.1.0
|
||||
area: system_documentation
|
||||
tags: [handbuch, authoring, methodik, obsidian, mindnet, best-practice]
|
||||
retriever_weight: 2.0
|
||||
|
|
@ -11,121 +11,145 @@ retriever_weight: 2.0
|
|||
|
||||
# Authoring Guidelines: Dein Werkzeug für den Digitalen Zwilling
|
||||
|
||||
Dieses Handbuch ist dein primäres Werkzeug, um Wissen so zu strukturieren, dass Mindnet deine Persönlichkeit spiegelt, empathisch reagiert und dich sowie deine Nachkommen strategisch berät. Es dient als Brücke zwischen deiner menschlichen Navigation in Obsidian und der technischen Logik der Mindnet-Engine.
|
||||
|
||||
Dieses Handbuch ist dein primäres Werkzeug, um Wissen so zu strukturieren, dass Mindnet deine Persönlichkeit spiegelt, empathisch reagiert und dich strategisch berät.
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Die 6 Goldenen Regeln (TL;DR)
|
||||
## ⚡ Die 6 Goldenen Regeln des Knowledge Designs
|
||||
|
||||
1. **Atomare Gedanken:** Eine Notiz = Ein Thema. Trenne z. B. „Meditation“ von „Mobility“.
|
||||
2. **Explizite Typen:** Nutze den `type` im Frontmatter (z. B. `insight`, `experience`, `value`), um die mathematische Gewichtung zu steuern.
|
||||
3. **H3-Hub-Pairing (NEU):** Nutze H3-Überschriften in Hubs, um spezifische Links und ihre Bedeutung (Edges) in isolierten Chunks für die KI zu fixieren, ohne die Obsidian-Graphen-Logik zu brechen.
|
||||
4. **Werte & Ziele definieren:** Erstelle für jeden Kernwert eine eigene Notiz (`type: value`). Ohne explizite Maßstäbe kann die Decision Engine nicht in deinem Sinne abwägen.
|
||||
5. **Emotionales Bridging:** Nutze Begriffe wie „Druck“, „Faszination“ oder „Angst“, um die Empathie-Ebene der KI zu aktivieren.
|
||||
6. **Narrative Tiefe (Fleisch am Knochen):** Dokumentiere das „Warum“ hinter einer Entscheidung. Erzählungen prägen deinen Charakter für die Nachwelt mehr als reine Fakten.
|
||||
1. **Atomare Gedanken:** Eine Notiz = Ein Thema. Trenne z. B. „Meditation“ von „Mobility“, auch wenn beides Ich-Pflege ist.
|
||||
2. **Explizite Typen:** Nutze den `type`, um der KI zu sagen, wie sie den Text verarbeiten soll (z. B. `insight` für Beobachtungen, `experience` für Erlebnisse).
|
||||
3. **Semantische Links:** Verknüpfe aktiv mit `[[rel:depends_on ...]]` oder `[[rel:based_on ...]]`. Sag dem System, *warum* Dinge zusammenhängen.
|
||||
4. **Werte & Ziele definieren:** Erstelle für jeden Nordstern und jeden Kernwert eine eigene Notiz. Ohne Maßstäbe kann der „Berater“ nicht entscheiden.
|
||||
5. **Emotionales Bridging:** Nutze Begriffe wie „Druck“, „Stolz“, „Euphorie“ oder „Hilflosigkeit“, um die Empathie-Ebene der KI zu aktivieren.
|
||||
6. **Narrative Tiefe (Fleisch am Knochen):** Dokumentiere das „Warum“ hinter einer Entscheidung. Fakten informieren, aber Erzählungen prägen den Charakter.
|
||||
|
||||
---
|
||||
|
||||
## 1. Die Vault-Architektur (Stream-Mapping)
|
||||
## 1. Strategische Steuerung (Status & Gewicht)
|
||||
|
||||
Der Vault ist in acht funktionale Domänen unterteilt, die direkt mit den internen Wissens-Streams korrespondieren.
|
||||
Du entscheidest über das Frontmatter, wie präsent eine Information im „Gedächtnis“ ist.
|
||||
|
||||
| Ordner | Domäne | Stream-Logik | Zweck |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **00_Leitbild** | Verfassung | **Identity** | Chronik deiner Werte-Evolution über die Jahre. |
|
||||
| **01_Identify** | Kern-Identität | **Identity** | SSOT für Werte, Prinzipien, Rollen, Bedürfnisse und Glaubenssätze. |
|
||||
| **02_Projects** | Dynamik | **Action** | Aktive Vorhaben, Missionen und operative Aufgaben (Tasks). |
|
||||
| **03_Experiences** | Biografie | **History** | Speicherort für Erlebnisse (Experiences), Ereignisse (Events) und Zustände (States). |
|
||||
| **04_Insights** | Erkenntnisse | **History/Basis** | Fachwissen, Konzepte, Ideen und tiefe Musteranalysen. |
|
||||
| **05_Decisions** | Steuerung | **Action** | Dokumentation getroffener Entscheidungen (ADR-Logik). |
|
||||
| **06_Skills** | Kompetenz | **Action** | Fertigkeiten, Lernpfade und Meisterschaftsnachweise. |
|
||||
| **07_People** | Soziales Netz | **History/Basis** | Kontaktprofile, Rollen und soziale Vernetzung. |
|
||||
### 1.1 Status-Logik
|
||||
* **`stable`**: Gold-Standard. Für finale Leitbild-Texte und Nordsterne. Erhält +20% Relevanz-Bonus.
|
||||
* **`active`**: Standard für laufende Projekte und aktuelle Beobachtungen.
|
||||
* **`draft`**: Brainstorming oder rohe Tages-Logs. Die KI nutzt diese nur nachrangig (50% Malus), um Rauschen zu vermeiden.
|
||||
* **`system`**: Rein technische Dateien (Templates, Guides). Werden im Chat ignoriert.
|
||||
|
||||
### 1.2 Manuelle Gewichtung (`retriever_weight`)
|
||||
* **Boost (1.20):** Für hochrelevante Erkenntnisse. **Beispiel:** Beobachtungen zum Verhalten von Rohan werden als `insight` mit `1.20` markiert, damit sie bei Erziehungsfragen sofort präsent sind.
|
||||
* **Deboost (0.50 - 0.80):** Für tägliche Routine-Einträge („Heute 10 Min. meditiert“). Sie dienen der Chronik, sollen aber keine tiefen Analysen verfälschen.
|
||||
|
||||
---
|
||||
|
||||
## 2. Das Schicht-Modell & Hub-Design
|
||||
## 2. Das Kochbuch: Praktische Use-Cases
|
||||
|
||||
In Hub-Notizen (z. B. „Wendepunkte“) nutzen wir eine hierarchische Schichtung, um Präzision für die KI und Übersicht für den Menschen zu garantieren.
|
||||
### 2.1 Ein Erlebnis aufschreiben (`type: experience`)
|
||||
**Ziel:** Den „Spiegel“ (Empathy) mit deiner Biografie kalibrieren.
|
||||
|
||||
### 2.1 Die H3-Regel für präzises Pairing
|
||||
Um Kausalitäten (z. B. Ereignis A führte zu Gefühl B) ohne proprietäre Syntax abzubilden, wird jedes Hauptelement eines Hubs in eine **H3-Sektion** gefasst.
|
||||
* **Technischer Effekt:** Das System nutzt für Hubs das Profil `structured_smart_edges_strict_L3` und schneidet an jeder H3-Ebene einen sauberen Chunk.
|
||||
* **Vorteil:** Callouts innerhalb dieser Sektion beziehen sich ausschließlich auf diesen Kontext, was die Antwortqualität massiv erhöht.
|
||||
**Struktur & Leitfragen:**
|
||||
- **Kontext:** Was ist passiert? (Sachlich kurz).
|
||||
- **Emotions-Check:** Wie habe ich mich in der Situation gefühlt? (Wichtig für Regel 5).
|
||||
- **Die Lektion:** Was habe ich über mich gelernt? (z. B. „Ich reagiere allergisch auf Ungerechtigkeit“).
|
||||
- **Deep-Edge:** Mit welcher Rolle ist das verknüpft? `[[rel:supports Meine Rollenlandkarte 2025#Vater]]`.
|
||||
|
||||
### 2.2 Die 3 Schichten im Detail
|
||||
* **Ebene 1: Cluster (Hub)**: Der Navigator (`type: insight`, `status: stable`). Fasst Lebensphasen oder Themenwelten zusammen.
|
||||
* **Ebene 2: Reflexion (Erlebnis)**: Die `experience`-Notiz. Die bewusste Aufarbeitung mit „Emotions-Check“ und „Lektion“.
|
||||
* **Ebene 3: Evidenz (Faktum)**: `event` oder `state`-Notizen. Die atomaren Rohdaten und Gefühle des Augenblicks (Momentaufnahmen).
|
||||
### 2.2 Eine Beobachtung festhalten (`type: insight`)
|
||||
**Ziel:** Den „Berater“ (Decision) mit Mustern versorgen.
|
||||
|
||||
**Beispiel Rohan-Beobachtung:**
|
||||
- **Beobachtung:** Rohan reagiert positiv auf „leise“ Impulse und 90/10 Coaching.
|
||||
- **Interpretation:** Direkte Konfrontation erzeugt Gegendruck; Fragen erzeugen Ownership.
|
||||
- **Konsequenz:** Prinzip P3a (Familienregeln) muss immer einen Bedürfnischeck vorausschicken.
|
||||
|
||||
### 2.3 Ein Review durchführen (`type: journal` / `insight`)
|
||||
**Ziel:** Den Fortschritt steuern.
|
||||
- **Daily:** Rohes Log (Status `draft`).
|
||||
- **Weekly/Monthly:** Verdichtung der Erkenntnisse in eine `stable` Notiz. Frage: „Was war der größte Hebel diese Woche?“.
|
||||
|
||||
### 2.4 Das Handlungsprinzip (`type: principle`)
|
||||
**Ziel:** Testbare Regeln für schwierige Entscheidungssituationen.
|
||||
|
||||
**Struktur-Vorgabe:**---
|
||||
id: 01-authoring-guidelines
|
||||
title: Authoring Guidelines – Das Handbuch für den Digitalen Zwilling
|
||||
type: principle
|
||||
status: stable
|
||||
version: 1.2.0
|
||||
area: system_documentation
|
||||
tags: [handbuch, authoring, methodik, obsidian, mindnet, best-practice]
|
||||
retriever_weight: 2.0
|
||||
---
|
||||
|
||||
## 3. Strategische Steuerung (Status & Gewicht)
|
||||
|
||||
Du entscheidest über das Frontmatter, wie präsent eine Information im „Gedächtnis“ des Systems ist.
|
||||
|
||||
* **`status: stable`**: Gold-Standard (+20% Relevanz-Bonus). Für finales Leitbild-Wissen und Kernwerte.
|
||||
* **`status: active`**: Standard für laufende Projekte und verifizierte Erlebnisse.
|
||||
* **`status: draft`**: Brainstorming oder rohe Tages-Logs. Erhält einen Malus (50%), um Rauschen zu vermeiden.
|
||||
* **`retriever_weight`**: Nutze `1.2` für Hubs und `1.1` für prägende Erlebnisse.
|
||||
|
||||
---
|
||||
|
||||
## 4. Netzdesign & Semantic Mapping
|
||||
## 🕸️ Teil 3: Netzdesign (Hubs, Edges & Lücken)
|
||||
|
||||
Ein intelligentes Netz wächst durch strategische Verknüpfungen, nicht durch Textmenge.
|
||||
|
||||
### 4.1 Zentrale Kanten (Edges)
|
||||
Nutze das kanonische Vokabular in `[!edge]` Callouts innerhalb der H3-Sektionen:
|
||||
* **`resulted_in` / `erzeugt`**: Verbindung zu einem daraus entstandenen Wert oder Gefühl.
|
||||
* **`caused_by` / `wegen`**: Dokumentiert die Ursache einer emotionalen Prägung oder Entscheidung.
|
||||
* **`part_of` / `gehört_zu`**: Bindet Details an einen übergeordneten Cluster oder Hub.
|
||||
* **`guides` / `steuert`**: Prinzipien oder Werte, die eine Sektion oder ein Vorhaben leiten.
|
||||
### 3.1 Wissens-Hubs (Zentralnotizen)
|
||||
Hubs fungieren als Verteilerzentren im Graphen.
|
||||
* **Struktur:** Nutze Überschriften (H2, H3) und verlinke von dort auf Detailnotizen.
|
||||
* **Deep-Edges:** Verlinke präzise Abschnitte: `[[Rollenlandkarte#Vater]]`. Dies ermöglicht der KI, nur den relevanten Kontext zu laden.
|
||||
|
||||
### 4.2 Forward-Mapping (Strategische Lücken)
|
||||
Setze bewusst Links auf Dateien, die noch nicht existieren (z. B. `[[Die beste Version meiner selbst]]`). Die KI erkennt diese Lücken und stellt proaktiv Fragen, um diese Felder gemeinsam mit dir zu füllen.
|
||||
### 3.2 Forward-Mapping (Strategische Lücken)
|
||||
Setze bewusst Links auf Dateien, die noch nicht existieren (z. B. `[[Die beste Version meiner selbst]]`).
|
||||
* **Zweck:** Du signalisierst dem System: „Hier entsteht ein wichtiges Konzept“.
|
||||
* **Effekt:** Die KI erkennt diese Lücken und kann im Chat proaktiv Fragen stellen, um diese Felder mit dir gemeinsam zu füllen.
|
||||
|
||||
---
|
||||
|
||||
## 5. Das Kochbuch: Praktische Use-Cases
|
||||
## 4. Obsidian Usability & Automatisierung
|
||||
|
||||
### 5.1 Ein Erlebnis aufschreiben (`type: experience`)
|
||||
**Ziel:** Den „Spiegel“ (Empathy) mit deiner Biografie kalibrieren.
|
||||
* **Struktur:** Kontext (Was ist passiert?), Emotions-Check (Gefühle?), Lektion (Was gelernt?).
|
||||
* **Deep-Edge:** Verknüpfe es immer mit einer Rolle: `[[rel:supports Meine Rollenlandkarte 2025#Vater]]`.
|
||||
### 4.1 Templater-Integration
|
||||
Nutze Vorlagen, die bereits die **Leitfragen** und das richtige **Retriever-Weight** enthalten. Ein Klick auf „Neues Erlebnis“ sollte automatisch das Frontmatter mit `type: experience` und `weight: 1.1` füllen.
|
||||
|
||||
### 5.2 Eine Beobachtung festhalten (`type: insight`)
|
||||
**Ziel:** Den „Berater“ (Decision) mit Mustern versorgen.
|
||||
* **Beispiel:** "Beobachtung: Rohan reagiert positiv auf leise Impulse" -> Konsequenz: Prinzip Bedürfnischeck.
|
||||
|
||||
### 5.3 Das Handlungsprinzip (`type: principle`)
|
||||
**Ziel:** Testbare Regeln für schwierige Situationen.
|
||||
* **Struktur:** Das Prinzip, Anwendung & Beispiele, Wächterfrage (Was frage ich mich im Moment der Entscheidung?).
|
||||
### 4.2 Meta Bind Dashboards
|
||||
Nutze **Meta Bind**, um Felder wie `status` oder `retriever_weight` über Schieberegler direkt in der Notiz zu steuern. Das macht die Gewichtung deines Wissens intuitiv und spielerisch.
|
||||
|
||||
---
|
||||
|
||||
## 6. Obsidian Usability & Automatisierung
|
||||
|
||||
### 6.1 Templater & Meta Bind
|
||||
* **Automatisierung:** Nutze Vorlagen, die bereits die Leitfragen und das richtige `retriever_weight` enthalten.
|
||||
* **Interaktive Steuerung:** Nutze Meta Bind, um Felder wie `status` oder `retriever_weight` über Schieberegler direkt in der Notiz zu steuern.
|
||||
|
||||
### 6.2 Deep-Edges & Verknüpfung
|
||||
* Verlinke Erlebnisse konsequent mit Rollen, Personen oder Clustern.
|
||||
* Nutze `derived_from`, um Prinzipien an ihre Ursprungssituation zu binden.
|
||||
|
||||
---
|
||||
|
||||
## 7. Vernetzung für die Mindnet-Personas
|
||||
## 5. Vernetzung für die Mindnet-Personas
|
||||
|
||||
| Persona | Notiz-Fokus | Effekt im Dialog |
|
||||
| :--- | :--- | :--- |
|
||||
| **🪞 Spiegel (Empathy)** | `states`, `journal`, `experience` | Erzeugt Resonanz durch Nachempfinden deiner Gefühle. |
|
||||
| **⚖️ Berater (Decision)** | `values`, `principles`, `decisions` | Wägt aktuelle Fragen gegen deine lebenslangen Maßstäbe ab. |
|
||||
| **📚 Bibliothekar (Facts)** | `events`, `concepts`, `sources`, `person` | Liefert präzise Fakten, historische Daten und soziale Kontexte. |
|
||||
| **🪞 Spiegel (Empathy)** | Gefühle & Biografisches | „Ich verstehe deinen Frust, das war bei Projekt X ähnlich...“. |
|
||||
| **⚖️ Berater (Decision)** | Werte & Wächterfragen | „Option A ist nicht ratsam, da sie gegen dein Prinzip der Integrität verstößt“. |
|
||||
| **📚 Bibliothekar (Facts)** | Struktur & Definitionen | „Deine Mission als Vater ist es, verlässliche Präsenz zu zeigen...“. |
|
||||
|
||||
---
|
||||
|
||||
## 8. Verbindungen von Notiztypen (Graphen-Logik)
|
||||
> [!abstract] Fazit für den Autor
|
||||
> Mindnet ist ein **Persönlichkeitsspiegel**. Dokumentiere weniger „Technik“ und mehr „Mensch“. Jede Notiz sollte die Frage beantworten: „Was sagt das über mich und meine Werte aus?“.
|
||||
- **3 Signale:** Woran merke ich im Alltag, dass ich das Prinzip lebe?.
|
||||
- **Wächterfrage:** Welche Frage stelle ich mir im Moment der Entscheidung?.
|
||||
|
||||
---
|
||||
|
||||
## 6. Obsidian Workflow-Hacks
|
||||
|
||||
### 6.1 Templater & Meta Bind
|
||||
- **Automatisierung:** Nutze das **Templater-Plugin**, um beim Erstellen einer Notiz sofort die passenden Leitfragen und das richtige `retriever_weight` einzufügen.
|
||||
- **Interaktive Steuerung:** Nutze **Meta Bind**, um Felder wie `status` oder `retriever_weight` über Buttons oder Slider direkt im Lesemodus zu ändern, ohne im YAML-Code zu tippen.
|
||||
|
||||
### 6.2 Deep-Edges & Verknüpfung
|
||||
- Verknüpfe Erlebnisse immer mit der entsprechenden Rolle: `[[rel:supports Meine Rollenlandkarte 2025#Vater]]`.
|
||||
- Nutze `derived_from`, um Prinzipien an ihre Ursprungs-Session zu binden.
|
||||
|
||||
---
|
||||
|
||||
## 7. Vernetzung für Mindnet Personas
|
||||
|
||||
| Persona | Notiz-Fokus | Beispiel |
|
||||
| :--- | :--- | :--- |
|
||||
| **🪞 Spiegel (Empathy)** | Emotionen & Prägungen | „Ich fühlte mich hilflos, als...“. |
|
||||
| **⚖️ Berater (Decision)** | Kriterien & Abwägungen | „Option A verletzt Wert X, weil...“. |
|
||||
| **📚 Bibliothekar (Facts)** | Struktur & Definition | „Rollenmission Vater bedeutet...“. |
|
||||
|
||||
---
|
||||
|
||||
> [!tip] Best Practice
|
||||
> Wenn du merkst, dass eine Notiz zu technisch wird: Halte inne und frage dich: „Was macht das mit mir als Mensch?“ Schreibe *das* auf. Das ist die Essenz für deinen KI-Zwilling.
|
||||
|
||||
## 8. Verbindungen von Notiztypen
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
|
|
@ -162,3 +186,4 @@ graph TD
|
|||
style V fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style EX fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style P fill:#bfb,stroke:#333,stroke-width:2px
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,25 @@
|
|||
---
|
||||
doc_type: user_manual
|
||||
audience: user, mindmaster
|
||||
scope: chat, ui, feedback, graph, agentic_validation
|
||||
scope: chat, ui, feedback, graph
|
||||
status: active
|
||||
version: 4.5.8
|
||||
context: "Anleitung zur Nutzung der Web-Oberfläche, der Chat-Personas, Multi-Stream RAG und des Graph Explorers. Inkludiert WP-24c Chunk-Aware Multigraph-System und automatische Spiegelkanten."
|
||||
version: 2.6
|
||||
context: "Anleitung zur Nutzung der Web-Oberfläche, der Chat-Personas und des Graph Explorers."
|
||||
---
|
||||
|
||||
# Chat & Graph Usage Guide
|
||||
|
||||
**Quellen:** `user_guide.md`
|
||||
|
||||
> 💡 **Tipp für Einsteiger:** Falls du noch nie mit Mindnet gearbeitet hast, starte mit dem [Schnellstart](../00_General/00_quickstart.md) und dem [Knowledge Design Manual](01_knowledge_design.md), um die Grundlagen zu verstehen.
|
||||
|
||||
## 1. Was Mindnet für dich tut
|
||||
|
||||
Mindnet ist ein **assoziatives Gedächtnis** mit Persönlichkeit. Es unterscheidet sich von einer reinen Suche dadurch, dass es **kontextsensitiv** agiert.
|
||||
|
||||
**Das Gedächtnis (Der Graph - Chunk-Aware Multigraph):**
|
||||
**Das Gedächtnis (Der Graph):**
|
||||
Wenn du nach "Projekt Alpha" suchst, findet Mindnet auch:
|
||||
* **Abhängigkeiten:** "Technologie X wird benötigt".
|
||||
* **Entscheidungen:** "Warum nutzen wir X?".
|
||||
* **Ähnliches:** "Projekt Beta war ähnlich".
|
||||
* **Beide Richtungen:** Dank automatischer Spiegelkanten findest du auch Notizen, die auf "Projekt Alpha" verweisen (z.B. "Projekt Beta enforced_by: Projekt Alpha").
|
||||
* **Präzise Abschnitte:** Deep-Links zu spezifischen Abschnitten (`[[Note#Section]]`) ermöglichen präzise Verknüpfungen innerhalb langer Dokumente.
|
||||
|
||||
**Der Zwilling (Die Personas):**
|
||||
Mindnet passt seinen Charakter an: Mal ist es der neutrale Bibliothekar, mal der strategische Berater, mal der empathische Spiegel.
|
||||
|
|
@ -62,49 +58,21 @@ Ein Editor mit **"File System First"** Garantie.
|
|||
|
||||
---
|
||||
|
||||
## 3. Den Chat steuern (Intents & Multi-Stream RAG)
|
||||
## 3. Den Chat steuern (Intents)
|
||||
|
||||
Du steuerst die Persönlichkeit von Mindnet durch deine Wortwahl. Seit WP-25 nutzt Mindnet **Agentic Multi-Stream RAG**, das deine Anfrage in parallele Wissens-Streams aufteilt:
|
||||
Du steuerst die Persönlichkeit von Mindnet durch deine Wortwahl. Der **Hybrid Router v5** unterscheidet intelligent:
|
||||
|
||||
### 3.1 Intent-Erkennung (Hybrid-Router)
|
||||
|
||||
Der Router erkennt deine Absicht auf zwei Wegen:
|
||||
|
||||
**Schnelle Erkennung (Keyword Fast-Path):**
|
||||
* **"Soll ich..."** → Sofortige Erkennung als `DECISION` (Berater)
|
||||
* **"Wann..."** → Sofortige Erkennung als `FACT_WHEN` (Zeitpunkte)
|
||||
* **"Was ist..."** → Sofortige Erkennung als `FACT_WHAT` (Wissen)
|
||||
* **"Ich fühle..."** → Sofortige Erkennung als `EMPATHY` (Spiegel)
|
||||
|
||||
**Intelligente Analyse (LLM Slow-Path):**
|
||||
* Bei unklaren Anfragen analysiert die KI semantisch deine Absicht
|
||||
|
||||
### 3.2 Multi-Stream Retrieval (WP-25)
|
||||
|
||||
Anstelle einer einzelnen Suche führt Mindnet nun **parallele Abfragen** in spezialisierten Wissens-Streams aus:
|
||||
|
||||
**Die Streams:**
|
||||
* **Values Stream:** Deine Identität, Ethik und Prinzipien (`value`, `principle`, `belief`)
|
||||
* **Facts Stream:** Operative Daten zu Projekten, Tasks und Status (`project`, `decision`, `task`)
|
||||
* **Biography Stream:** Persönliche Erfahrungen und Journal-Einträge (`experience`, `journal`)
|
||||
* **Risk Stream:** Hindernisse und potenzielle Gefahren (`risk`, `obstacle`)
|
||||
* **Tech Stream:** Technisches Wissen, Code und Dokumentation (`concept`, `source`, `glossary`)
|
||||
|
||||
**Vorteil:** Jeder Stream fokussiert auf spezifische Wissensbereiche, was zu präziseren und kontextreicheren Antworten führt.
|
||||
|
||||
### 3.3 Frage-Modi (Strategien)
|
||||
### 3.1 Frage-Modus (Wissen abrufen)
|
||||
Ausgelöst durch `?` oder W-Wörter.
|
||||
|
||||
* **Entscheidung ("Soll ich?"):** Der **Berater**.
|
||||
* Nutzt: Values Stream, Facts Stream, Risk Stream
|
||||
* Wägt Fakten gegen deine Werte ab und evaluiert Risiken
|
||||
* Mindnet lädt deine Werte (`type: value`) und Ziele (`type: goal`).
|
||||
* *Beispiel:* "Soll ich Tool X nutzen?" -> "Nein, Tool X speichert Daten in den USA. Das verstößt gegen dein Prinzip 'Privacy First'."
|
||||
* **Empathie ("Ich fühle..."):** Der **Spiegel**.
|
||||
* Nutzt: Biography Stream, Values Stream
|
||||
* Greift auf deine Erfahrungen und Werte zurück
|
||||
* Mindnet lädt deine Erfahrungen (`type: experience`).
|
||||
* *Beispiel:* "Ich bin frustriert." -> "Das erinnert mich an Projekt Y, da ging es uns ähnlich..."
|
||||
* **Fakten ("Was ist?", "Wann..."):** Der **Bibliothekar**.
|
||||
* Nutzt: Facts Stream, Tech Stream, Biography Stream
|
||||
* Liefert präzise Definitionen und zeitliche Informationen
|
||||
* **Fakten ("Was ist?"):** Der **Bibliothekar**.
|
||||
* Liefert präzise Definitionen.
|
||||
|
||||
### 3.2 Befehls-Modus (Interview)
|
||||
Ausgelöst durch Aussagen wie "Neues Projekt", "Ich will festhalten".
|
||||
|
|
@ -114,17 +82,13 @@ Ausgelöst durch Aussagen wie "Neues Projekt", "Ich will festhalten".
|
|||
|
||||
---
|
||||
|
||||
## 4. Ergebnisse interpretieren (Explanation Layer & Stream-Tracing)
|
||||
## 4. Ergebnisse interpretieren (Explanation Layer)
|
||||
|
||||
Mindnet liefert eine **Begründung** ("Reasons"), warum es etwas gefunden hat.
|
||||
Öffne eine Quellen-Karte, um zu sehen:
|
||||
* *"Hohe textuelle Übereinstimmung."* (Semantik)
|
||||
* *"Bevorzugt aufgrund des Typs 'decision'."* (Wichtigkeit)
|
||||
* *"Verweist auf 'Projekt X' via 'depends_on'."* (Graph-Kontext)
|
||||
* *"Quelle: Values Stream"* (Stream-Tracing - WP-25)
|
||||
|
||||
**Stream-Tracing (WP-25):**
|
||||
Jede Quelle wird mit ihrem Ursprungs-Stream markiert (z.B. "Values Stream", "Facts Stream"). Dies hilft dir zu verstehen, aus welchem Wissensbereich die Information stammt.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -160,85 +124,3 @@ Wenn du im **manuellen Editor** schreibst, unterstützt dich Mindnet aktiv bei d
|
|||
* **Semantische Treffer:** Das System findet inhaltlich verwandte Notizen.
|
||||
* **Klick auf "➕ Einfügen":** Fügt den Link (z.B. `[[rel:related_to Mindnet]]`) an der Cursor-Position ein.
|
||||
4. **Speichern:** Der Text wird sofort in den Vault geschrieben und indiziert.
|
||||
|
||||
### 6.3 Weg C: Direktes Schreiben in Obsidian
|
||||
Wenn du Obsidian nutzt, kannst du Notizen direkt im Vault erstellen. Nach dem Speichern in Obsidian:
|
||||
|
||||
1. **Automatischer Import (Cron):** Der stündliche Cron-Job importiert neue/geänderte Dateien automatisch.
|
||||
2. **Manueller Import:** Oder führe manuell aus:
|
||||
```bash
|
||||
python3 -m scripts.import_markdown --vault ./vault --prefix mindnet --apply
|
||||
```
|
||||
|
||||
**Vorteil:** Du arbeitest in deiner gewohnten Obsidian-Umgebung, Mindnet synchronisiert im Hintergrund.
|
||||
|
||||
---
|
||||
|
||||
## 7. Häufige Anwendungsfälle (Use Cases)
|
||||
|
||||
### 7.1 Entscheidungshilfe suchen
|
||||
**Szenario:** Du stehst vor einer schwierigen Entscheidung.
|
||||
|
||||
**Vorgehen:**
|
||||
1. Stelle eine Frage im Chat: *"Soll ich Tool X nutzen?"*
|
||||
2. Mindnet erkennt `DECISION` Intent
|
||||
3. System lädt deine Werte, Prinzipien und Ziele
|
||||
4. Antwort berücksichtigt deine persönlichen Kriterien
|
||||
|
||||
**Beispiel:** *"Soll ich Notion nutzen?"* → System prüft gegen dein Prinzip "Privacy First" und rät ab.
|
||||
|
||||
### 7.2 Erfahrungen dokumentieren
|
||||
**Szenario:** Du willst ein prägendes Erlebnis festhalten.
|
||||
|
||||
**Vorgehen:**
|
||||
1. Chat: *"Neue Erfahrung: Streit am Recyclinghof"*
|
||||
2. System erstellt strukturierte Notiz mit Feldern:
|
||||
- Situation
|
||||
- Reaktion
|
||||
- Learning
|
||||
3. Verknüpfe mit relevanten Rollen: `[[rel:supports Meine Rollenlandkarte 2025#Vater]]`
|
||||
|
||||
### 7.3 Wissen im Graph erkunden
|
||||
**Szenario:** Du willst verstehen, wie Konzepte zusammenhängen.
|
||||
|
||||
**Vorgehen:**
|
||||
1. Wechsle zum **Graph Explorer**
|
||||
2. Suche nach einem Knoten (z.B. "Projekt Alpha")
|
||||
3. Klicke auf "🎯 Als Zentrum setzen"
|
||||
4. Erkunde die Nachbarschaft (Abhängigkeiten, Entscheidungen, ähnliche Projekte)
|
||||
|
||||
### 7.4 Bestehende Notizen verbessern
|
||||
**Szenario:** Eine Notiz ist unvollständig oder unvernetzt.
|
||||
|
||||
**Vorgehen:**
|
||||
1. Öffne die Notiz im **Manuellen Editor**
|
||||
2. Nutze **Intelligence-Tab** für Link-Vorschläge
|
||||
3. Füge semantische Links hinzu: `[[rel:based_on Wert X]]`
|
||||
4. Speichern → System indiziert neu
|
||||
|
||||
---
|
||||
|
||||
## 8. Tipps & Tricks
|
||||
|
||||
### 8.1 Intent-Steuerung
|
||||
- **Frage mit "?":** Löst RAG-Modus aus (Wissen abrufen)
|
||||
- **Aussage ohne "?":** Löst Interview-Modus aus (Wissen speichern)
|
||||
- **Emotionale Wörter:** Aktivieren Empathie-Persona ("Ich fühle...", "Ich bin frustriert...")
|
||||
|
||||
### 8.2 Graph-Navigation
|
||||
- **URL-Persistenz:** Graph-Einstellungen werden in der URL gespeichert → Lesezeichen für spezifische Ansichten
|
||||
- **Deep-Links:** Verlinke präzise Abschnitte: `[[Note#Abschnitt]]` statt nur `[[Note]]`
|
||||
|
||||
### 8.3 Feedback nutzen
|
||||
- **Regelmäßiges Feedback:** Macht das System schlauer
|
||||
- **Granulares Feedback:** Bewerte einzelne Quellen, nicht nur die Gesamtantwort
|
||||
- **Konsequenz:** System lernt deine Präferenzen
|
||||
|
||||
---
|
||||
|
||||
## 9. Weitere Informationen
|
||||
|
||||
- **Knowledge Design:** Siehe [Knowledge Design Manual](01_knowledge_design.md)
|
||||
- **Authoring Guidelines:** Siehe [Authoring Guidelines](01_authoring_guidelines.md)
|
||||
- **Obsidian Integration:** Siehe [Obsidian Integration Guide](01_obsidian_integration_guide.md)
|
||||
- **Troubleshooting:** Siehe [Admin Operations - Troubleshooting](../04_Operations/04_admin_operations.md#33-troubleshooting-guide)
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
doc_type: user_manual
|
||||
audience: user, author
|
||||
scope: vault, markdown, schema, agentic_validation, note_scope
|
||||
scope: vault, markdown, schema
|
||||
status: active
|
||||
version: 4.5.8
|
||||
context: "Regelwerk für das Erstellen von Notizen im Vault. Die 'Source of Truth' für Autoren. Inkludiert WP-24c Phase 3 Agentic Edge Validation, automatische Spiegelkanten und Note-Scope Zonen."
|
||||
version: 2.8.0
|
||||
context: "Regelwerk für das Erstellen von Notizen im Vault. Die 'Source of Truth' für Autoren."
|
||||
---
|
||||
|
||||
# Knowledge Design Manual
|
||||
|
|
@ -208,12 +208,6 @@ Dies ist die **mächtigste** Methode. Du sagst dem System explizit, **wie** Ding
|
|||
> "Daher [[rel:depends_on Qdrant]]."
|
||||
> "Dieses Konzept ist [[rel:similar_to Pinecone]]."
|
||||
|
||||
**Deep-Links zu Abschnitten (v2.9.1):**
|
||||
Du kannst auch auf spezifische Abschnitte innerhalb einer Note verlinken:
|
||||
> "Siehe [[rel:based_on Mein Leitbild#P3 – Disziplin]]."
|
||||
|
||||
Das System trennt automatisch den Note-Namen (`Mein Leitbild`) vom Abschnitts-Namen (`P3 – Disziplin`), sodass mehrere Links zur gleichen Note möglich sind, wenn sie auf verschiedene Abschnitte zeigen.
|
||||
|
||||
**Gültige Relationen:**
|
||||
* `depends_on`: Hängt ab von / Benötigt.
|
||||
* `blocks`: Blockiert oder gefährdet (z.B. Risiko -> Projekt).
|
||||
|
|
@ -232,20 +226,8 @@ Für Zusammenfassungen am Ende einer Notiz, oder eines Absatzes:
|
|||
> [[AI Agents]]
|
||||
```
|
||||
|
||||
**Multi-Line Support (v2.9.1):**
|
||||
Callout-Blocks mit mehreren Zeilen werden korrekt verarbeitet. Das System erkennt automatisch, wenn mehrere Links im gleichen Callout-Block stehen, und erstellt für jeden Link eine separate Kante (auch bei Deep-Links zu verschiedenen Sections).
|
||||
|
||||
**Format-agnostische De-Duplizierung:**
|
||||
Wenn Kanten bereits via `[!edge]` Callout vorhanden sind, werden sie nicht mehrfach injiziert. Das System erkennt vorhandene Kanten unabhängig vom Format (Inline, Callout, Wikilink).
|
||||
|
||||
### 4.3 Automatische Spiegelkanten (Invers-Logik) - WP-24c v4.5.8
|
||||
|
||||
In Mindnet musst du Kanten **nicht** manuell in beide Richtungen pflegen. Das System erzeugt automatisch **Spiegelkanten** (Invers-Kanten) im Hintergrund.
|
||||
|
||||
**Wie es funktioniert:**
|
||||
1. **Du setzt eine explizite Kante:** Z.B. `[[rel:depends_on Projekt Alpha]]` in Note A
|
||||
2. **System erzeugt automatisch die Spiegelkante:** Note "Projekt Alpha" erhält automatisch `enforced_by: Note A`
|
||||
3. **Vorteil:** Beide Richtungen sind durchsuchbar, ohne dass du beide manuell setzen musst
|
||||
### 4.3 Implizite Bidirektionalität (Edger-Logik) [NEU] [PRÜFEN!]
|
||||
In Mindnet musst du Kanten **nicht** manuell in beide Richtungen pflegen. Der **Edger** übernimmt die Paarbildung automatisch im Hintergrund.
|
||||
|
||||
**Deine Aufgabe:** Setze die Kante in der Datei, die du gerade bearbeitest, so wie es der **logische Fluss** vorgibt.
|
||||
|
||||
|
|
@ -253,112 +235,10 @@ In Mindnet musst du Kanten **nicht** manuell in beide Richtungen pflegen. Das Sy
|
|||
* **Blick nach vorn (Vorwärtslink):** Wenn du einen Plan oder ein Protokoll schreibst, nutze `resulted_in`, `supports` oder `next`.
|
||||
|
||||
**System-Logik (Beispiele):**
|
||||
- Schreibst du in Note A: `[[rel:next Projekt B]]`, erzeugt das System automatisch: `Projekt B prev: Note A`
|
||||
- Schreibst du in Note B: `[[rel:derived_from Note A]]`, erzeugt das System automatisch: `Note A resulted_in: Note B`
|
||||
- Schreibst du in Note A: `[[rel:impacts Projekt B]]`, erzeugt das System automatisch: `Projekt B impacted_by: Note A`
|
||||
- Schreibst du in Note A: `next: [[B]]`, weiß das System automatisch: `B prev A`.
|
||||
- Schreibst du in Note B: `derived_from: [[A]]`, weiß das System automatisch: `A resulted_in B`.
|
||||
|
||||
**Wichtig:**
|
||||
- **Explizite Kanten haben Vorrang:** Wenn du bereits beide Richtungen explizit gesetzt hast, wird keine automatische Spiegelkante erzeugt (keine Duplikate)
|
||||
- **Höhere Wirksamkeit expliziter Kanten:** Explizit gesetzte Kanten haben höhere Priorität und Confidence-Werte als automatisch generierte Spiegelkanten
|
||||
- **Schutz vor Manipulation:** System-Kanten (`belongs_to`, `next`, `prev`) können nicht manuell überschrieben werden (Provenance Firewall)
|
||||
|
||||
**Vorteil:** Keine redundante Datenpflege, kein "Link-Nightmare", volle Konsistenz im Graphen. Beide Richtungen sind durchsuchbar, was die Auffindbarkeit von Informationen verdoppelt.
|
||||
|
||||
### 4.4 Explizite vs. Validierte Kanten (Phase 3 Validierung) - WP-24c v4.5.8
|
||||
|
||||
Mindnet unterscheidet zwischen **expliziten Kanten** (sofort übernommen) und **validierten Kanten** (Phase 3 LLM-Prüfung).
|
||||
|
||||
#### Explizite Kanten (Höchste Priorität)
|
||||
|
||||
Diese Kanten werden **sofort** in den Graph übernommen, ohne LLM-Validierung:
|
||||
|
||||
1. **Typed Relations im Text:**
|
||||
```markdown
|
||||
Diese Entscheidung [[rel:depends_on Performance-Analyse]] wurde getroffen.
|
||||
```
|
||||
|
||||
2. **Callout-Edges:**
|
||||
```markdown
|
||||
> [!edge] depends_on
|
||||
> [[Performance-Analyse]]
|
||||
> [[Projekt Alpha]]
|
||||
```
|
||||
|
||||
3. **Note-Scope Zonen:**
|
||||
```markdown
|
||||
## Smart Edges
|
||||
[[rel:depends_on|System-Architektur]]
|
||||
[[rel:part_of|Gesamt-System]]
|
||||
```
|
||||
*(Siehe auch: [Note-Scope Zonen](NOTE_SCOPE_ZONEN.md))*
|
||||
|
||||
**Vorteil expliziter Kanten:**
|
||||
- ✅ **Sofortige Übernahme:** Keine Wartezeit auf LLM-Validierung
|
||||
- ✅ **Höchste Priorität:** Werden immer beibehalten, auch bei Duplikaten
|
||||
- ✅ **Höhere Confidence:** Explizite Kanten haben `confidence: 1.0` (maximal)
|
||||
- ✅ **Keine Validierungs-Kosten:** Keine LLM-Aufrufe erforderlich
|
||||
|
||||
#### Validierte Kanten (Phase 3 - candidate: Präfix)
|
||||
|
||||
Kanten, die in speziellen Validierungs-Zonen stehen, erhalten das `candidate:` Präfix und werden in **Phase 3** durch ein LLM semantisch geprüft:
|
||||
|
||||
**Format:**
|
||||
```markdown
|
||||
### Unzugeordnete Kanten
|
||||
|
||||
related_to:Mögliche Verbindung
|
||||
depends_on:Unsicherer Link
|
||||
uses:Experimentelle Technologie
|
||||
```
|
||||
|
||||
**Validierungsprozess:**
|
||||
1. **Extraktion:** Links aus `### Unzugeordnete Kanten` erhalten `candidate:` Präfix
|
||||
2. **Phase 3 Validierung:** LLM prüft semantisch: "Passt diese Verbindung zum Kontext?"
|
||||
3. **Erfolg (VERIFIED):** `candidate:` Präfix wird entfernt, Kante wird persistiert
|
||||
4. **Ablehnung (REJECTED):** Kante wird **nicht** in die Datenbank geschrieben
|
||||
|
||||
**Kontext-Optimierung:**
|
||||
- **Note-Scope Kanten:** LLM nutzt Note-Summary oder gesamten Note-Text (besser für globale Verbindungen)
|
||||
- **Chunk-Scope Kanten:** LLM nutzt spezifischen Chunk-Text (besser für lokale Referenzen)
|
||||
|
||||
**Wann nutze ich validierte Kanten?**
|
||||
- ✅ **Explorative Verbindungen:** Du bist unsicher, ob die Verbindung wirklich passt
|
||||
- ✅ **Experimentelle Links:** Du willst testen, ob eine Verbindung semantisch Sinn macht
|
||||
- ✅ **Automatische Vorschläge:** Das System hat Links vorgeschlagen, die du prüfen lassen willst
|
||||
|
||||
**Wann nutze ich explizite Kanten?**
|
||||
- ✅ **Sichere Verbindungen:** Du bist dir sicher, dass die Verbindung korrekt ist
|
||||
- ✅ **Schnelle Übernahme:** Du willst keine Wartezeit auf Validierung
|
||||
- ✅ **Höchste Priorität:** Die Verbindung soll definitiv im Graph sein
|
||||
|
||||
*(Siehe auch: [LLM-Validierung von Links](LLM_VALIDIERUNG_VON_LINKS.md))*
|
||||
|
||||
### 4.5 Note-Scope Zonen (Globale Verbindungen) - WP-24c v4.2.0
|
||||
|
||||
Für Verbindungen, die der **gesamten Note** zugeordnet werden sollen (nicht nur einem spezifischen Chunk), nutze **Note-Scope Zonen**:
|
||||
|
||||
```markdown
|
||||
## Smart Edges
|
||||
|
||||
[[rel:depends_on|Projekt-Übersicht]]
|
||||
[[rel:part_of|Größeres System]]
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- ✅ **Globale Verbindungen:** Links gelten für die gesamte Note, nicht nur einen Abschnitt
|
||||
- ✅ **Höchste Priorität:** Note-Scope Links haben Vorrang bei Duplikaten
|
||||
- ✅ **Bessere Validierung:** In Phase 3 nutzt das LLM den gesamten Note-Kontext (Note-Summary/Text)
|
||||
|
||||
**Wann nutze ich Note-Scope?**
|
||||
- ✅ **Projekt-Abhängigkeiten:** "Dieses Projekt hängt von X ab" (gilt für die ganze Note)
|
||||
- ✅ **System-Zugehörigkeit:** "Dieses Konzept ist Teil von Y" (gilt für die ganze Note)
|
||||
- ✅ **Globale Prinzipien:** "Diese Entscheidung basiert auf Prinzip Z" (gilt für die ganze Note)
|
||||
|
||||
**Wann nutze ich Chunk-Scope (Standard)?**
|
||||
- ✅ **Lokale Referenzen:** "In diesem Abschnitt nutzen wir Technologie X" (nur für diesen Abschnitt)
|
||||
- ✅ **Spezifische Kontexte:** Links, die nur in einem bestimmten Textabschnitt relevant sind
|
||||
|
||||
*(Siehe auch: [Note-Scope Zonen - Detaillierte Anleitung](NOTE_SCOPE_ZONEN.md))*
|
||||
**Vorteil:** Keine redundante Datenpflege, kein "Link-Nightmare", volle Konsistenz im Graphen.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,282 +0,0 @@
|
|||
# LLM-Validierung von Links in Notizen (Phase 3 Agentic Edge Validation)
|
||||
|
||||
**Version:** v4.5.8
|
||||
**Status:** Aktiv
|
||||
**Aktualisiert:** WP-24c Phase 3 Agentic Edge Validation mit Kontext-Optimierung
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Mindnet-System unterstützt zwei Arten von Links:
|
||||
|
||||
1. **Explizite Links** - Werden direkt übernommen (keine Validierung)
|
||||
2. **Global Pool Links** - Werden vom LLM validiert (wenn aktiviert)
|
||||
|
||||
## Explizite Links (keine Validierung)
|
||||
|
||||
Diese Links werden **sofort** in den Graph übernommen, ohne LLM-Validierung:
|
||||
|
||||
### 1. Typed Relations
|
||||
```markdown
|
||||
[[rel:mastered_by|Klaus]]
|
||||
[[rel:depends_on|Projekt Alpha]]
|
||||
```
|
||||
|
||||
### 2. Standard Wikilinks
|
||||
```markdown
|
||||
[[Klaus]]
|
||||
[[Projekt Alpha]]
|
||||
```
|
||||
|
||||
### 3. Callouts
|
||||
```markdown
|
||||
> [!edge] mastered_by:Klaus
|
||||
> [!edge] depends_on:Projekt Alpha
|
||||
```
|
||||
|
||||
**Hinweis:** Explizite Links haben immer Vorrang und werden nicht validiert.
|
||||
|
||||
## Validierte Links (Phase 3 - candidate: Präfix) - WP-24c v4.5.8
|
||||
|
||||
Links, die vom LLM validiert werden sollen, müssen in einer speziellen Sektion am Ende der Notiz definiert werden. Diese Links erhalten das `candidate:` Präfix und durchlaufen **Phase 3 Agentic Edge Validation**.
|
||||
|
||||
### Format
|
||||
|
||||
Erstellen Sie eine Sektion mit einem der folgenden Titel:
|
||||
- `### Unzugeordnete Kanten`
|
||||
- `### Edge Pool`
|
||||
- `### Candidates`
|
||||
|
||||
In dieser Sektion listen Sie Links im Format `kind:target` auf:
|
||||
|
||||
```markdown
|
||||
---
|
||||
type: concept
|
||||
title: Meine Notiz
|
||||
---
|
||||
|
||||
# Inhalt der Notiz
|
||||
|
||||
Hier ist der normale Inhalt...
|
||||
|
||||
### Unzugeordnete Kanten
|
||||
|
||||
related_to:Klaus
|
||||
mastered_by:Projekt Alpha
|
||||
depends_on:Andere Notiz
|
||||
```
|
||||
|
||||
### Beispiel
|
||||
|
||||
```markdown
|
||||
---
|
||||
type: decision
|
||||
title: Entscheidung über Technologie-Stack
|
||||
---
|
||||
|
||||
# Entscheidung über Technologie-Stack
|
||||
|
||||
Wir haben uns für React entschieden, weil...
|
||||
|
||||
## Begründung
|
||||
|
||||
React bietet bessere Performance...
|
||||
|
||||
### Unzugeordnete Kanten
|
||||
|
||||
related_to:React-Dokumentation
|
||||
depends_on:Performance-Analyse
|
||||
uses:TypeScript
|
||||
```
|
||||
|
||||
### Validierung
|
||||
|
||||
**Wichtig:** Global Pool Links werden nur validiert, wenn:
|
||||
|
||||
1. Die Chunk-Konfiguration `enable_smart_edge_allocation: true` enthält
|
||||
2. Dies wird normalerweise in `config/types.yaml` pro Note-Typ konfiguriert
|
||||
|
||||
**Beispiel-Konfiguration in `types.yaml`:**
|
||||
|
||||
```yaml
|
||||
types:
|
||||
decision:
|
||||
chunking_profile: sliding_smart_edges
|
||||
chunking:
|
||||
sliding_smart_edges:
|
||||
enable_smart_edge_allocation: true # ← Aktiviert LLM-Validierung
|
||||
```
|
||||
|
||||
### Phase 3 Validierungsprozess (WP-24c v4.5.8)
|
||||
|
||||
1. **Extraktion:** Links aus der "Unzugeordnete Kanten" Sektion werden extrahiert
|
||||
2. **candidate: Präfix:** Erhalten `candidate:` Präfix in `rule_id` oder `provenance`
|
||||
3. **Kontext-Optimierung:**
|
||||
- **Note-Scope (`scope: note`):** LLM nutzt `note_summary` (Top 5 Chunks) oder `note_text` (aggregierter Gesamttext)
|
||||
- **Chunk-Scope (`scope: chunk`):** LLM nutzt spezifischen Chunk-Text, falls verfügbar, sonst Note-Text
|
||||
4. **Validierung:** LLM prüft semantisch (via `ingest_validator` Profil, Temperature 0.0):
|
||||
- Ist der Link semantisch relevant für den Kontext?
|
||||
- Passt die Relation (`kind`) zum Ziel?
|
||||
5. **Ergebnis:**
|
||||
- ✅ **VERIFIED:** `candidate:` Präfix wird entfernt, Kante wird in den Graph übernommen
|
||||
- 🚫 **REJECTED:** Kante wird **nicht** in die Datenbank geschrieben (verhindert "Geister-Verknüpfungen")
|
||||
|
||||
### Validierungs-Prompt
|
||||
|
||||
Das System verwendet den Prompt `edge_validation` aus `config/prompts.yaml`:
|
||||
|
||||
```
|
||||
Verify relation '{edge_kind}' for graph integrity.
|
||||
Chunk: "{chunk_text}"
|
||||
Target: "{target_title}" ({target_summary})
|
||||
Respond ONLY with 'YES' or 'NO'.
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Empfohlen
|
||||
|
||||
1. **Explizite Links für sichere Verbindungen:**
|
||||
```markdown
|
||||
Diese Entscheidung [[rel:depends_on|Performance-Analyse]] wurde getroffen.
|
||||
```
|
||||
|
||||
2. **Global Pool für unsichere/explorative Links:**
|
||||
```markdown
|
||||
### Unzugeordnete Kanten
|
||||
related_to:Mögliche Verbindung
|
||||
```
|
||||
|
||||
3. **Kombination beider Ansätze:**
|
||||
```markdown
|
||||
# Hauptinhalt
|
||||
|
||||
Explizite Verbindung: [[rel:depends_on|Sichere Notiz]]
|
||||
|
||||
## Weitere Überlegungen
|
||||
|
||||
### Unzugeordnete Kanten
|
||||
related_to:Unsichere Verbindung
|
||||
explored_in:Experimentelle Notiz
|
||||
```
|
||||
|
||||
### ❌ Vermeiden
|
||||
|
||||
1. **Nicht zu viele Global Pool Links:**
|
||||
- Jeder Link erfordert einen LLM-Aufruf
|
||||
- Kann die Ingestion verlangsamen
|
||||
|
||||
2. **Nicht für offensichtliche Links:**
|
||||
- Nutzen Sie explizite Links für klare Verbindungen
|
||||
- Global Pool ist für explorative/unsichere Links gedacht
|
||||
|
||||
## Aktivierung der Validierung
|
||||
|
||||
### Schritt 1: Chunk-Profile konfigurieren
|
||||
|
||||
In `config/types.yaml`:
|
||||
|
||||
```yaml
|
||||
types:
|
||||
your_type:
|
||||
chunking_profile: sliding_smart_edges
|
||||
chunking:
|
||||
sliding_smart_edges:
|
||||
enable_smart_edge_allocation: true
|
||||
```
|
||||
|
||||
### Schritt 2: Notiz erstellen
|
||||
|
||||
```markdown
|
||||
---
|
||||
type: your_type
|
||||
title: Meine Notiz
|
||||
---
|
||||
|
||||
# Inhalt
|
||||
|
||||
### Unzugeordnete Kanten
|
||||
|
||||
related_to:Ziel-Notiz
|
||||
```
|
||||
|
||||
### Schritt 3: Import ausführen
|
||||
|
||||
```bash
|
||||
python3 -m scripts.import_markdown --vault ./vault --apply
|
||||
```
|
||||
|
||||
## Logging & Debugging (Phase 3)
|
||||
|
||||
Während der Ingestion sehen Sie im Log:
|
||||
|
||||
```
|
||||
🚀 [PHASE 3] Validierung: Note-A -> Ziel-Notiz (related_to) | Scope: chunk | Kontext: Chunk-Scope (c00)
|
||||
⚖️ [VALIDATING] Relation 'related_to' -> 'Ziel-Notiz' (Profile: ingest_validator)...
|
||||
✅ [PHASE 3] VERIFIED: Note-A -> Ziel-Notiz (related_to) | rule_id: explicit
|
||||
```
|
||||
|
||||
oder
|
||||
|
||||
```
|
||||
🚀 [PHASE 3] Validierung: Note-A -> Ziel-Notiz (related_to) | Scope: note | Kontext: Note-Scope (aggregiert)
|
||||
⚖️ [VALIDATING] Relation 'related_to' -> 'Ziel-Notiz' (Profile: ingest_validator)...
|
||||
🚫 [PHASE 3] REJECTED: Note-A -> Ziel-Notiz (related_to)
|
||||
```
|
||||
|
||||
**Hinweis:** Phase 3 Logs zeigen auch die Kontext-Optimierung (Note-Scope vs. Chunk-Scope) und den finalen Status (VERIFIED/REJECTED).
|
||||
|
||||
## Technische Details
|
||||
|
||||
### Provenance-System (WP-24c v4.5.8)
|
||||
|
||||
- `explicit`: Explizite Links (keine Validierung, höchste Priorität)
|
||||
- `explicit:note_zone`: Note-Scope Links aus `## Smart Edges` (keine Validierung)
|
||||
- `candidate:`: Links aus `### Unzugeordnete Kanten` (Phase 3 Validierung erforderlich)
|
||||
- `semantic_ai`: KI-generierte Links
|
||||
- `rule`: Regel-basierte Links (z.B. aus types.yaml)
|
||||
- `structure`: System-generierte Spiegelkanten (automatische Invers-Logik)
|
||||
|
||||
### Code-Referenzen
|
||||
|
||||
- **Extraktion:** `app/core/chunking/chunking_processor.py` (Zeile 66-81)
|
||||
- **Validierung:** `app/core/ingestion/ingestion_validation.py`
|
||||
- **Integration:** `app/core/ingestion/ingestion_processor.py` (Zeile 237-239)
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Werden explizite Links auch validiert?**
|
||||
A: Nein, explizite Links werden direkt übernommen.
|
||||
|
||||
**Q: Kann ich die Validierung für bestimmte Links überspringen?**
|
||||
A: Ja, nutzen Sie explizite Links (`[[rel:kind|target]]` oder `> [!edge]`).
|
||||
|
||||
**Q: Was passiert, wenn das LLM nicht verfügbar ist?**
|
||||
A: Das System unterscheidet zwischen:
|
||||
- **Transienten Fehlern (Netzwerk, Timeout):** Kante wird erlaubt (Integrität vor Präzision - verhindert Datenverlust)
|
||||
- **Permanenten Fehlern (Config, Validation):** Kante wird abgelehnt (Graph-Qualität schützen)
|
||||
|
||||
**Q: Was ist der Unterschied zwischen expliziten und validierten Links?**
|
||||
A:
|
||||
- **Explizite Links:** Sofortige Übernahme, höchste Priorität, keine Validierung, `confidence: 1.0`
|
||||
- **Validierte Links:** Phase 3 Prüfung, `candidate:` Präfix, können abgelehnt werden, höhere Graph-Qualität
|
||||
|
||||
**Q: Warum sollte ich explizite Links nutzen statt validierte?**
|
||||
A: Explizite Links haben:
|
||||
- ✅ Sofortige Übernahme (keine Wartezeit)
|
||||
- ✅ Höchste Priorität (werden immer beibehalten)
|
||||
- ✅ Keine Validierungs-Kosten (keine LLM-Aufrufe)
|
||||
- ✅ Höhere Confidence-Werte
|
||||
|
||||
Nutze validierte Links nur, wenn du unsicher bist, ob die Verbindung wirklich passt.
|
||||
|
||||
**Q: Kann ich mehrere Links in einer Zeile angeben?**
|
||||
A: Nein, jeder Link muss in einer eigenen Zeile stehen: `kind:target`.
|
||||
|
||||
## Zusammenfassung (WP-24c v4.5.8)
|
||||
|
||||
- ✅ **Explizite Links:** `[[rel:kind|target]]`, `> [!edge]` oder `## Smart Edges` → Keine Validierung, höchste Priorität
|
||||
- ✅ **Validierte Links:** Sektion `### Unzugeordnete Kanten` → Phase 3 Validierung mit `candidate:` Präfix
|
||||
- ✅ **Phase 3 Validierung:** LLM prüft semantisch mit Kontext-Optimierung (Note-Scope vs. Chunk-Scope)
|
||||
- ✅ **Ergebnis:** VERIFIED (Präfix entfernt, persistiert) oder REJECTED (nicht in DB geschrieben)
|
||||
- ✅ **Format:** `kind:target` (eine pro Zeile in `### Unzugeordnete Kanten`)
|
||||
- ✅ **Automatische Spiegelkanten:** Explizite Kanten erzeugen automatisch Invers-Kanten (beide Richtungen durchsuchbar)
|
||||
|
|
@ -1,275 +0,0 @@
|
|||
# Note-Scope Extraktions-Zonen (v4.5.8)
|
||||
|
||||
**Version:** v4.5.8
|
||||
**Status:** Aktiv
|
||||
**Aktualisiert:** WP-24c Phase 3 Agentic Edge Validation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Mindnet-System unterstützt nun **Note-Scope Extraktions-Zonen**, die es ermöglichen, Links zu definieren, die der gesamten Note zugeordnet werden (nicht nur einem spezifischen Chunk).
|
||||
|
||||
### Unterschied: Chunk-Scope vs. Note-Scope
|
||||
|
||||
- **Chunk-Scope Links** (`scope: "chunk"`):
|
||||
- Werden aus dem Text-Inhalt extrahiert
|
||||
- Sind lokalem Kontext zugeordnet
|
||||
- `source_id` = `chunk_id`
|
||||
|
||||
- **Note-Scope Links** (`scope: "note"`):
|
||||
- Werden aus speziellen Markdown-Sektionen extrahiert
|
||||
- Sind der gesamten Note zugeordnet
|
||||
- `source_id` = `note_id`
|
||||
- Haben höchste Priorität bei Duplikaten
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Format
|
||||
|
||||
Erstellen Sie eine Sektion mit einem der folgenden Header:
|
||||
|
||||
- `## Smart Edges`
|
||||
- `## Relationen`
|
||||
- `## Global Links`
|
||||
- `## Note-Level Relations`
|
||||
- `## Globale Verbindungen`
|
||||
|
||||
**Wichtig:** Die Header müssen exakt (case-insensitive) übereinstimmen.
|
||||
|
||||
### Beispiel
|
||||
|
||||
```markdown
|
||||
---
|
||||
type: decision
|
||||
title: Technologie-Entscheidung
|
||||
---
|
||||
|
||||
# Entscheidung über Technologie-Stack
|
||||
|
||||
Wir haben uns für React entschieden...
|
||||
|
||||
## Begründung
|
||||
|
||||
React bietet bessere Performance...
|
||||
|
||||
## Smart Edges
|
||||
|
||||
[[rel:depends_on|Performance-Analyse]]
|
||||
[[rel:uses|TypeScript]]
|
||||
[[React-Dokumentation]]
|
||||
|
||||
## Weitere Überlegungen
|
||||
|
||||
Hier ist weiterer Inhalt...
|
||||
```
|
||||
|
||||
### Unterstützte Link-Formate
|
||||
|
||||
In Note-Scope Zonen werden folgende Formate unterstützt:
|
||||
|
||||
1. **Typed Relations:**
|
||||
```markdown
|
||||
## Smart Edges
|
||||
[[rel:depends_on|Ziel-Notiz]]
|
||||
[[rel:uses|Andere Notiz]]
|
||||
```
|
||||
|
||||
2. **Standard Wikilinks:**
|
||||
```markdown
|
||||
## Smart Edges
|
||||
[[Ziel-Notiz]]
|
||||
[[Andere Notiz]]
|
||||
```
|
||||
(Werden als `related_to` interpretiert)
|
||||
|
||||
3. **Callouts:**
|
||||
```markdown
|
||||
## Smart Edges
|
||||
> [!edge] depends_on:[[Ziel-Notiz]]
|
||||
> [!edge] uses:[[Andere Notiz]]
|
||||
```
|
||||
|
||||
## Technische Details
|
||||
|
||||
### ID-Generierung
|
||||
|
||||
Note-Scope Links verwenden die **exakt gleiche ID-Generierung** wie Symmetrie-Kanten in Phase 2:
|
||||
|
||||
```python
|
||||
_mk_edge_id(kind, note_id, target_id, "note", target_section=sec)
|
||||
```
|
||||
|
||||
Dies stellt sicher, dass:
|
||||
- ✅ Authority-Check in Phase 2 korrekt funktioniert
|
||||
- ✅ Keine Duplikate entstehen
|
||||
- ✅ Symmetrie-Schutz greift
|
||||
|
||||
### Provenance
|
||||
|
||||
Note-Scope Links erhalten:
|
||||
- `provenance: "explicit:note_zone"`
|
||||
- `confidence: 1.0` (höchste Priorität)
|
||||
- `scope: "note"`
|
||||
- `source_id: note_id` (nicht `chunk_id`)
|
||||
|
||||
### Priorisierung
|
||||
|
||||
Bei Duplikaten (gleiche ID):
|
||||
1. **Note-Scope Links** haben **höchste Priorität**
|
||||
2. Dann Confidence-Wert
|
||||
3. Dann Provenance-Priority
|
||||
|
||||
**Beispiel:**
|
||||
- Chunk-Link: `related_to:Note-A` (aus Text)
|
||||
- Note-Scope Link: `related_to:Note-A` (aus Zone)
|
||||
- **Ergebnis:** Note-Scope Link wird beibehalten
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Empfohlen
|
||||
|
||||
1. **Note-Scope für globale Verbindungen:**
|
||||
```markdown
|
||||
## Smart Edges
|
||||
[[rel:depends_on|Projekt-Übersicht]]
|
||||
[[rel:part_of|Größeres System]]
|
||||
```
|
||||
|
||||
2. **Chunk-Scope für lokale Referenzen:**
|
||||
```markdown
|
||||
In diesem Abschnitt verweisen wir auf [[rel:uses|Spezifische Technologie]].
|
||||
```
|
||||
|
||||
3. **Kombination:**
|
||||
```markdown
|
||||
# Hauptinhalt
|
||||
|
||||
Lokale Referenz: [[rel:uses|Lokale Notiz]]
|
||||
|
||||
## Smart Edges
|
||||
|
||||
Globale Verbindung: [[rel:depends_on|Globale Notiz]]
|
||||
```
|
||||
|
||||
### ❌ Vermeiden
|
||||
|
||||
1. **Nicht für lokale Kontext-Links:**
|
||||
- Nutzen Sie Chunk-Scope Links für lokale Referenzen
|
||||
- Note-Scope ist für Note-weite Verbindungen gedacht
|
||||
|
||||
2. **Nicht zu viele Note-Scope Links:**
|
||||
- Beschränken Sie sich auf wirklich Note-weite Verbindungen
|
||||
- Zu viele Note-Scope Links können die Graph-Struktur verwässern
|
||||
|
||||
## Integration mit Phase 3 Validierung (WP-24c v4.5.8)
|
||||
|
||||
Note-Scope Links können **zwei verschiedene Provenance** haben:
|
||||
|
||||
### Explizite Note-Scope Links (Keine Validierung)
|
||||
|
||||
Links in `## Smart Edges` Zonen werden als `explicit:note_zone` markiert und **direkt übernommen** (keine Phase 3 Validierung):
|
||||
|
||||
```markdown
|
||||
## Smart Edges
|
||||
|
||||
[[rel:depends_on|System-Architektur]]
|
||||
[[rel:part_of|Gesamt-System]]
|
||||
```
|
||||
|
||||
**Vorteil:** Sofortige Übernahme, höchste Priorität, keine Validierungs-Kosten.
|
||||
|
||||
### Validierte Note-Scope Links (Phase 3 Validierung)
|
||||
|
||||
Links in `### Unzugeordnete Kanten` erhalten `candidate:` Präfix und werden in **Phase 3** validiert:
|
||||
|
||||
```markdown
|
||||
### Unzugeordnete Kanten
|
||||
|
||||
related_to:Mögliche Verbindung
|
||||
depends_on:Unsicherer Link
|
||||
```
|
||||
|
||||
**Validierungsprozess:**
|
||||
1. Links erhalten `candidate:` Präfix
|
||||
2. **Phase 3 Validierung:** LLM prüft semantisch gegen Note-Summary oder Note-Text (Note-Scope Kontext-Optimierung)
|
||||
3. **Erfolg (VERIFIED):** `candidate:` Präfix wird entfernt, Kante wird persistiert
|
||||
4. **Ablehnung (REJECTED):** Kante wird **nicht** in die Datenbank geschrieben
|
||||
|
||||
**Wichtig:**
|
||||
- Links in `### Unzugeordnete Kanten` werden als `candidate:` markiert und durchlaufen Phase 3
|
||||
- Links in `## Smart Edges` werden als `explicit:note_zone` markiert und **nicht** validiert (direkt übernommen)
|
||||
- **Note-Scope Kontext-Optimierung:** Bei Note-Scope Kanten nutzt Phase 3 `note_summary` (Top 5 Chunks) oder `note_text` (aggregierter Gesamttext) für bessere Validierungs-Genauigkeit
|
||||
|
||||
## Beispiel: Vollständige Notiz
|
||||
|
||||
```markdown
|
||||
---
|
||||
type: decision
|
||||
title: Architektur-Entscheidung
|
||||
---
|
||||
|
||||
# Architektur-Entscheidung
|
||||
|
||||
Wir haben uns für Microservices entschieden...
|
||||
|
||||
## Begründung
|
||||
|
||||
### Performance
|
||||
|
||||
Microservices bieten bessere Skalierbarkeit. Siehe auch [[rel:uses|Kubernetes]] für Orchestrierung.
|
||||
|
||||
### Sicherheit
|
||||
|
||||
Wir nutzen [[rel:enforced_by|OAuth2]] für Authentifizierung.
|
||||
|
||||
## Smart Edges
|
||||
|
||||
[[rel:depends_on|System-Architektur]]
|
||||
[[rel:part_of|Gesamt-System]]
|
||||
[[rel:uses|Cloud-Infrastruktur]]
|
||||
|
||||
## Weitere Details
|
||||
|
||||
Hier ist weiterer Inhalt...
|
||||
```
|
||||
|
||||
**Ergebnis:**
|
||||
- `uses:Kubernetes` → Chunk-Scope (aus Text)
|
||||
- `enforced_by:OAuth2` → Chunk-Scope (aus Text)
|
||||
- `depends_on:System-Architektur` → Note-Scope (aus Zone)
|
||||
- `part_of:Gesamt-System` → Note-Scope (aus Zone)
|
||||
- `uses:Cloud-Infrastruktur` → Note-Scope (aus Zone)
|
||||
|
||||
## Code-Referenzen
|
||||
|
||||
- **Extraktion:** `app/core/graph/graph_derive_edges.py` → `extract_note_scope_zones()`
|
||||
- **Integration:** `app/core/graph/graph_derive_edges.py` → `build_edges_for_note()`
|
||||
- **Header-Liste:** `NOTE_SCOPE_ZONE_HEADERS` in `graph_derive_edges.py`
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Können Note-Scope Links auch Section-Links sein?**
|
||||
A: Ja, `[[rel:kind|Target#Section]]` wird unterstützt. `target_section` fließt in die ID ein.
|
||||
|
||||
**Q: Was passiert, wenn ein Link sowohl in Chunk als auch in Note-Scope Zone steht?**
|
||||
A: Der Note-Scope Link hat Vorrang und wird beibehalten.
|
||||
|
||||
**Q: Werden Note-Scope Links validiert?**
|
||||
A: Das hängt von der Zone ab:
|
||||
- **`## Smart Edges`:** Nein, werden direkt übernommen (explizite Links, keine Validierung)
|
||||
- **`### Unzugeordnete Kanten`:** Ja, durchlaufen Phase 3 Validierung (candidate: Präfix)
|
||||
|
||||
**Q: Was ist der Unterschied zwischen Note-Scope in Smart Edges vs. Unzugeordnete Kanten?**
|
||||
A:
|
||||
- **Smart Edges:** Explizite Links, sofortige Übernahme, höchste Priorität
|
||||
- **Unzugeordnete Kanten:** Validierte Links, Phase 3 Prüfung, candidate: Präfix
|
||||
|
||||
**Q: Kann ich eigene Header-Namen verwenden?**
|
||||
A: Aktuell nur die vordefinierten Header. Erweiterung möglich durch Anpassung von `NOTE_SCOPE_ZONE_HEADERS`.
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
- ✅ **Note-Scope Zonen:** `## Smart Edges` oder ähnliche Header
|
||||
- ✅ **Format:** `[[rel:kind|target]]` oder `[[target]]`
|
||||
- ✅ **Scope:** `scope: "note"`, `source_id: note_id`
|
||||
- ✅ **Priorität:** Höchste Priorität bei Duplikaten
|
||||
- ✅ **ID-Konsistenz:** Exakt wie Symmetrie-Kanten (Phase 2)
|
||||
|
|
@ -1,394 +0,0 @@
|
|||
# Mindnet Causal Assistant – Dokumentation der bisher erreichten Resultate (0.4.x) + Architektur, Konfiguration & Strategien
|
||||
|
||||
> Stand: basierend auf den beobachteten Chain-Inspector-Logs und den zuletzt beschriebenen Implementierungen in 0.4.6/0.4.x.
|
||||
> Ziel dieser Doku: Eine **einheitliche, belastbare Basis**, damit Weiterentwicklung (0.5.x/0.6.x) nicht mehr “im Kreis” läuft.
|
||||
|
||||
---
|
||||
|
||||
## 1) Zweck & Gesamtziel von Mindnet
|
||||
|
||||
Mindnet soll in einem Obsidian Vault **kausale/argumentative Zusammenhänge** als Graph abbilden und daraus **nützliche Diagnosen** ableiten:
|
||||
|
||||
- **Graph-Aufbau:** Notes/Sections als Knoten, Links/Kanten als gerichtete Beziehungen (z.B. *wirkt_auf*, *resulted_in*, *depends_on* …).
|
||||
- **Analyse aus einem Kontext:** Nutzer steht in einer Note an einer bestimmten Überschrift/Section → Mindnet analysiert lokale Nachbarschaft + Pfade im Graphen.
|
||||
- **Template Matching:** Einordnen der gefundenen Knoten/Kanten in “Kettenmuster” (Chain Templates) wie z.B. *trigger → transformation → outcome* oder *loop_learning*.
|
||||
- **Findings (Gap-Heuristiken):** Hinweise wie “fehlende Slots”, “fehlende Links”, “unmapped edge types”, “einseitige Konnektivität”, etc.
|
||||
→ Ziel: **Nutzer konkret zum besseren Graphen führen**, ohne “Noisy” zu sein.
|
||||
|
||||
---
|
||||
|
||||
## 2) Begriffe & Datenmodell (so arbeitet der Chain Inspector)
|
||||
|
||||
### 2.1 Kontext (Context)
|
||||
Der Chain Inspector läuft immer gegen einen **aktuellen Kontext**:
|
||||
- `file`: aktuelle Note (z.B. `Tests/03_insight_transformation.md`)
|
||||
- `heading`: aktuelle Section (z.B. `Kern`)
|
||||
- `zoneKind`: i.d.R. `content`
|
||||
|
||||
Das ist wichtig, weil Kanten teils **section-spezifisch** sind und teils (geplant/teilweise offen) **note-weit** gelten könnten.
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Knoten (Nodes)
|
||||
Ein Knoten ist im Report meist referenziert als:
|
||||
- `file + heading` (z.B. `Tests/01_experience_trigger.md:Kontext`)
|
||||
- plus abgeleitete Metadaten wie `noteType` (z.B. experience, insight, decision, event)
|
||||
|
||||
**noteType** ist entscheidend fürs Template Matching (Slots).
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Kanten (Edges)
|
||||
Eine Kante hat typischerweise:
|
||||
- `rawEdgeType`: Original-Typ aus Markdown/Notation (z.B. `wirkt_auf`, `resulted_in`, `depends_on`, `derived_from`, …)
|
||||
- `from`: Quelle (file:heading)
|
||||
- `to`: Ziel (file:heading)
|
||||
- `scope`: Gültigkeit / Herkunft
|
||||
- `section`: Edge ist “voll gültig” für die Section
|
||||
- `candidate`: Edge ist nur Kandidat/unsicher, wird optional zugelassen
|
||||
- (geplant/offen) `note`: Edge gilt note-weit (unabhängig von Section)
|
||||
- `evidence`: Fundstelle (file, sectionHeading, lineRange)
|
||||
|
||||
---
|
||||
|
||||
## 3) Erreichte Resultate in 0.4.x (verifiziert)
|
||||
|
||||
### 3.1 includeCandidates – Kandidatenkanten funktionieren wie erwartet
|
||||
**Ergebnis (bereits mehrfach in Logs verifiziert):**
|
||||
- Wenn `includeCandidates=false`, werden Kanten mit `scope: candidate` **im effektiven Graphen ausgefiltert**.
|
||||
- Wenn `includeCandidates=true`, werden Kandidatenkanten **als incoming/outgoing** berücksichtigt und tauchen in `neighbors`/`paths` auf.
|
||||
|
||||
**Implikation:**
|
||||
- Das System kann “unsichere” oder “LLM-vorschlagene” Verbindungen existieren lassen, ohne in jedem Lauf die Analyse zu verfälschen.
|
||||
- In “Discovery” kann man Kandidaten zulassen (mehr Explorationspower).
|
||||
- In “Decisioning” kann man Kandidaten typischerweise sperren (mehr Verlässlichkeit).
|
||||
|
||||
---
|
||||
|
||||
### 3.2 required_links: Strict vs Soft Mode – missing_link_constraints Unterdrückung ist umgesetzt
|
||||
**Problem (historisch):**
|
||||
- `missing_link_constraints` wurde teilweise auch dann ausgegeben, wenn `required_links=false` (Soft Mode) aktiv war → unnötig “noisy”.
|
||||
|
||||
**Fix (laut Cursor-Report umgesetzt + Tests):**
|
||||
- `missing_link_constraints` wird nur erzeugt, wenn `effectiveRequiredLinks === true`.
|
||||
- Es gibt eine definierte Auflösungsreihenfolge für `required_links`:
|
||||
|
||||
**Resolution Order (effective required_links):**
|
||||
1. `template.matching?.required_links`
|
||||
2. `profile.required_links`
|
||||
3. `defaults.matching?.required_links`
|
||||
4. Fallback: `false`
|
||||
|
||||
**Transparenz bleibt erhalten:**
|
||||
- `satisfiedLinks` und `requiredLinks` werden weiterhin im Report angezeigt.
|
||||
- `linksComplete` bleibt als technischer Wert im Report bestehen.
|
||||
- **Nur** das Finding `missing_link_constraints` wird unterdrückt, nicht die Fakten.
|
||||
|
||||
**Implikation:**
|
||||
- Soft Mode (= required_links=false) ist jetzt ruhig genug, um “Entdeckung” zu unterstützen.
|
||||
- Strict Mode (= required_links=true) eignet sich für harte Qualitätskontrolle.
|
||||
|
||||
---
|
||||
|
||||
### 3.3 “Healthy graph” → Findings leer ([]), Template “confirmed”
|
||||
Wenn Slots **und** geforderte Links erfüllt sind (z.B. `trigger_transformation_outcome` mit 2/2 Links),
|
||||
dann ist `findings: []` das erwartete Ergebnis.
|
||||
|
||||
**Implikation:**
|
||||
- Das ist das zentrale “Green Path”-Signal: Graph ist konsistent für das gewählte Template/Profil.
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Unmapped edges werden erkannt (Diagnose)
|
||||
Wenn ein `rawEdgeType` nicht in die kanonischen Rollen/Edge-Rollen abgebildet werden kann, tauchen typischerweise Diagnosen auf:
|
||||
- `edgesUnmapped > 0`
|
||||
- Findings wie `no_causal_roles` oder link constraints bleiben unerfüllt
|
||||
|
||||
**Implikation:**
|
||||
- Das Rollen-Mapping (chain_roles.yaml) ist “critical path”: wenn ein Edge-Typ nicht gemappt wird, bricht oft der kausale Interpretationspfad.
|
||||
|
||||
---
|
||||
|
||||
## 4) Was ist (noch) offen – echtes nächstes Verifikationsziel
|
||||
|
||||
### 4.1 Note-Level Edges / Note-Scope (“für jede Sektion gültig”)
|
||||
Es existiert ein Konzept/Einbau: In `02_event_trigger_detail` gibt es einen Bereich, der Kanten **auf Note-Ebene** definieren soll (unabhängig von aktueller Section).
|
||||
|
||||
**Offen ist die robuste Verifikation (oder Implementierung), dass:**
|
||||
- diese Edges auch dann gelten, wenn der Cursor in einer anderen Section derselben Note steht,
|
||||
- idealerweise mit klar erkennbarer Kennzeichnung wie `scope: note` im Report.
|
||||
|
||||
**Warum ist das wichtig?**
|
||||
- Das ermöglicht “globaler Kontext” pro Note, ohne alles in jede Section duplizieren zu müssen.
|
||||
- Es ist eine UX-Optimierung: Nutzer kann “Meta-Verbindungen” an einer Stelle pflegen.
|
||||
|
||||
---
|
||||
|
||||
## 5) Konfigurationsdateien – Rolle & Interpretation (Mindnet-Strategie)
|
||||
|
||||
> Die folgenden Bereiche beschreiben eine **saubere, konsistente Interpretation**, wie Mindnet die Configs verwenden sollte.
|
||||
> Konkrete Keys, die in Logs sichtbar waren (z.B. required_links, min_slots_filled_for_gap_findings, min_score_for_gap_findings) sind hier berücksichtigt.
|
||||
|
||||
### 5.1 chain_templates.yaml – “Welche Ketten gibt es, wie werden sie gematcht?”
|
||||
**Zweck:**
|
||||
- Definiert Templates (Muster), z.B.:
|
||||
- `trigger_transformation_outcome`
|
||||
- `loop_learning`
|
||||
- ggf. weitere (constraint_to_adaptation usw.)
|
||||
|
||||
**Template enthält typischerweise:**
|
||||
- Slots (Rollen für Knoten): z.B. `trigger`, `transformation`, `outcome`, `experience`, `learning`, `behavior`, `feedback`
|
||||
- Required Link Constraints (welche Slot-zu-Slot Verbindungen zwingend sind)
|
||||
- Scoring/Matching-Parameter (ggf. weights, thresholds)
|
||||
- Optional: template-level override für `required_links`
|
||||
|
||||
**Matching-Profile (wie in Logs sichtbar):**
|
||||
- z.B. Profile: `discovery`, `decisioning`
|
||||
- Parameter im Profil (sichtbar in Logs):
|
||||
- `required_links` (strict vs soft)
|
||||
- `min_slots_filled_for_gap_findings`
|
||||
- `min_score_for_gap_findings`
|
||||
- ggf. `maxTemplateMatches`
|
||||
|
||||
**Interpretation:**
|
||||
- Templates liefern die “Soll-Struktur”
|
||||
- Profile bestimmen “Wie streng” wir die Soll-Struktur im jeweiligen Workflow bewerten
|
||||
|
||||
---
|
||||
|
||||
### 5.2 chain_roles.yaml – “Welche rawEdgeTypes zählen als welche Rollen?”
|
||||
**Zweck:**
|
||||
- Mappt `rawEdgeType` → kanonische Rollen/EdgeRoles (z.B. `causal`, `influences`, `enables_constraints`, `provenance`).
|
||||
- Diese Rollen sind Grundlage für:
|
||||
- `no_causal_roles` Finding
|
||||
- Link-Constraint-Satisfaction (Template erwartet “causal” zwischen Slots)
|
||||
- Matching Score (welche Edges zählen für welches Template)
|
||||
|
||||
**Interpretation:**
|
||||
- Wenn ein Edge-Typ nicht gemappt ist:
|
||||
- Edge kann trotzdem im Graph auftauchen,
|
||||
- aber Template/Constraint-Logik kann ihn nicht “verstehen” → führt zu Findings.
|
||||
|
||||
---
|
||||
|
||||
### 5.3 analysis_policies.yaml – “Wie noisy dürfen Findings sein?”
|
||||
**Zweck:**
|
||||
- Zentrale Policies für Findings:
|
||||
- welche Finding-Codes existieren
|
||||
- Default-Severity (info/warn/error)
|
||||
- Profilabhängige Overrides
|
||||
- Unterdrückungsregeln (z.B. suppress in soft mode, suppress wenn confirmed, suppress wenn Score hoch…)
|
||||
|
||||
**Interpretation:**
|
||||
- Policies sind “produktseitige UX-Regeln”:
|
||||
- Discovery: eher informativ, weniger warn
|
||||
- Decisioning: klare Warnungen, wenn Qualität fehlt
|
||||
- Der bereits umgesetzte Fix (`missing_link_constraints` nur in strict) ist exakt so eine Policy-Entscheidung (auch wenn technisch im Inspector gelöst).
|
||||
|
||||
---
|
||||
|
||||
## 6) Ablauf des Chain Inspectors (Vorgehensweise in Mindnet)
|
||||
|
||||
Hier ist ein konsistenter “Pipeline”-Ablauf, der zu den Logs passt:
|
||||
|
||||
### Schritt 1: Kontext bestimmen
|
||||
- Aktuelle Datei + aktuelle Section/Heading
|
||||
|
||||
### Schritt 2: Edges aus aktueller Note laden
|
||||
- Outgoing aus der aktuellen Section extrahieren (oder aus einem definierten Block)
|
||||
- (optional/offen) Note-Level Edges ebenfalls laden und für jede Section gültig machen
|
||||
|
||||
### Schritt 3: Nachbarn laden
|
||||
- Backlinks (Notes, die auf die aktuelle Note verlinken) → incoming Kandidatenquellen
|
||||
- Outgoing Neighbor Notes (Notes, auf die aktuelle Note verweist) → Nachbarschaft erweitern
|
||||
|
||||
### Schritt 4: Edges aus Neighbor Notes laden
|
||||
- Aus den verlinkenden Notes die Edges extrahieren, die auf die aktuelle Note/Section zielen
|
||||
- Canonicalization: rawEdgeTypes via chain_roles.yaml in Rollen überführen
|
||||
|
||||
### Schritt 5: Kandidatenfilter / Scopefilter anwenden
|
||||
- Wenn `includeCandidates=false`:
|
||||
- `scope: candidate` aus effective graph entfernen
|
||||
- Optional weitere Filter:
|
||||
- includeNoteLinks / includeSectionLinks
|
||||
- direction (forward/backward/both)
|
||||
- maxDepth (Traversal)
|
||||
|
||||
### Schritt 6: Pfade berechnen (Paths)
|
||||
- Forward/Backward (oder both)
|
||||
- BFS/DFS bis `maxDepth`
|
||||
- Resultat: Pfadlisten mit nodes + edges
|
||||
|
||||
### Schritt 7: Template Matching
|
||||
- Kandidatenknoten für Slots finden (via noteType + Nähe + Pfade)
|
||||
- Links/Constraints prüfen (erwartete slot→slot Beziehungen)
|
||||
- Score berechnen (z.B. per:
|
||||
- Slots erfüllt
|
||||
- Link constraints erfüllt
|
||||
- “RoleEvidence” passend)
|
||||
|
||||
### Schritt 8: Findings berechnen (Gap-Heuristics)
|
||||
Beispiele:
|
||||
- `missing_slot_*` wenn wichtige Slots fehlen (abhängig von Profil-Thresholds)
|
||||
- `one_sided_connectivity` wenn nur incoming oder nur outgoing
|
||||
- `no_causal_roles` wenn Edges da, aber keine causal Rollen im effektiven Graph
|
||||
- `missing_link_constraints` nur wenn effectiveRequiredLinks=true und slotsComplete=true, requiredLinks>0, linksComplete=false
|
||||
|
||||
### Schritt 9: Report ausgeben
|
||||
- context, settings, neighbors, paths, findings, analysisMeta, templateMatches
|
||||
- Transparenz: satisfiedLinks/requiredLinks/linksComplete bleiben sichtbar
|
||||
|
||||
---
|
||||
|
||||
## 7) Strategien, die Mindnet verfolgen kann (Produkt-/UX-Strategie)
|
||||
|
||||
### Strategie A: Discovery (Exploration)
|
||||
**Ziel:** Möglichst schnell “wo könnte eine sinnvolle Kette entstehen?” finden.
|
||||
- required_links = false (Soft Mode)
|
||||
- includeCandidates = true (optional)
|
||||
- findings eher informativ (info), weniger warn
|
||||
- Templates mehr als Vorschläge (“plausible/weak”), nicht als harte Bewertung
|
||||
|
||||
**Vorteil:** Nutzer bekommt schnell Hypothesen.
|
||||
**Risiko:** Mehr Noise, mehr falsche Kandidaten – muss per Policy gedämpft werden.
|
||||
|
||||
---
|
||||
|
||||
### Strategie B: Decisioning (Qualitätskontrolle)
|
||||
**Ziel:** Prüfung, ob eine Kette “wirklich steht” und als belastbar gelten kann.
|
||||
- required_links = true (Strict Mode)
|
||||
- includeCandidates = false
|
||||
- findings: warn, wenn Slots/Links fehlen
|
||||
- “confirmed” nur wenn link constraints komplett
|
||||
|
||||
**Vorteil:** Qualitätssicherung & Verlässlichkeit.
|
||||
**Risiko:** Nutzer fühlt sich “blockiert”, wenn Graph noch im Aufbau ist.
|
||||
|
||||
---
|
||||
|
||||
### Strategie C: Progressive Disclosure (hybrid)
|
||||
**Ziel:** Nutzer nicht überfordern, aber zielgerichtet verbessern.
|
||||
- Soft Mode für Einstieg
|
||||
- Button/Toggle: “Strict prüfen”
|
||||
- Candidate Edges als Vorschlag-Klasse (UI: “proposed edges”)
|
||||
- Findings priorisieren: erst fehlende Slots, dann fehlende Links, dann Detail-Qualität
|
||||
|
||||
---
|
||||
|
||||
## 8) Wie ein “kausaler Retriever” funktionieren könnte (Causal Retriever)
|
||||
|
||||
Ein kausaler Retriever ist die Komponente, die aus dem Vault/Graphen **relevante Kausalkontexte** für den aktuellen Abschnitt liefert – idealerweise deterministisch, skalierbar und template-aware.
|
||||
|
||||
### 8.1 Retrieval-Ziele
|
||||
- Finde Knoten/Edges, die **kausal relevant** sind zum aktuellen Kontext:
|
||||
- Ursachen (backward)
|
||||
- Wirkungen/Entscheidungen (forward)
|
||||
- Bedingungen/Constraints (seitlich)
|
||||
- Gib nicht nur Knoten zurück, sondern:
|
||||
- Pfade (explainable)
|
||||
- Evidence (wo steht das)
|
||||
- Role-Interpretation (warum ist das causal/influences/etc.)
|
||||
|
||||
### 8.2 Retrieval-Inputs
|
||||
- startNode = current section
|
||||
- direction = forward/backward/both
|
||||
- maxDepth
|
||||
- roleFilter (optional): nur causal/influences/enables_constraints
|
||||
- scopeFilter: includeCandidates, includeNoteLevel
|
||||
- templateBias: bevorzugte Pfadformen (z.B. “experience→insight→decision”)
|
||||
|
||||
### 8.3 Retrieval-Algorithmus (praktisch)
|
||||
**Variante 1: BFS mit Rolle-Gewichtung**
|
||||
- BFS über Kanten
|
||||
- Priorität/Score pro Frontier:
|
||||
- causal > influences > provenance
|
||||
- section-scope > note-scope > candidate (wenn candidates eingeschaltet, sonst candidate=∞)
|
||||
- Stop, wenn:
|
||||
- maxDepth erreicht
|
||||
- genug Top-N Pfade gesammelt (z.B. topNUsed)
|
||||
|
||||
**Variante 2: Template-driven Retrieval**
|
||||
- Wenn ein Template im Fokus ist:
|
||||
- suche explizit nach Slot-Knoten (noteType matching)
|
||||
- suche dann die minimalen Verbindungen, die Constraints erfüllen
|
||||
- Gute Option für “Decisioning”: deterministisch prüfen.
|
||||
|
||||
**Variante 3: Two-phase Retrieval**
|
||||
1) Kandidaten finden (Slots)
|
||||
2) Verbindungen prüfen (Constraints)
|
||||
→ Liefert sehr gut “warum fehlt Link X?” Diagnosen.
|
||||
|
||||
### 8.4 Output-Format
|
||||
- `neighbors` (incoming/outgoing, mit evidence)
|
||||
- `paths` (forward/backward, nodes+edges)
|
||||
- plus “slot candidates” optional (für UI)
|
||||
|
||||
---
|
||||
|
||||
## 9) Empfehlungen für robuste Tests (damit ihr nicht wieder im Kreis lauft)
|
||||
|
||||
### Was ist bereits ausreichend getestet (nicht wiederholen)
|
||||
- includeCandidates Filterverhalten ✅
|
||||
- missing_link_constraints Unterdrückung bei required_links=false ✅
|
||||
- strict/soft required_links via profile/template override ✅
|
||||
- “healthy graph” ergibt findings: [] ✅
|
||||
- unmapped edge type triggert Diagnose ✅
|
||||
|
||||
### Was als einziges “neues” Testziel für Abschluss 0.4.x/Start 0.5.x taugt
|
||||
- **Note-level edges / note-scope**: gelten Kanten “global” pro Note oder nicht?
|
||||
|
||||
**Minimal-Testdefinition (einmalig, reproduzierbar):**
|
||||
1) In `02_event_trigger_detail.md` einen klaren Note-Level Block definieren (z.B. “## Note-Verbindungen”).
|
||||
2) Edge dort definieren, die auf eine andere Note/Section zeigt.
|
||||
3) Cursor in einer anderen Section derselben Note platzieren (z.B. “## Detail” oder “## Extra”).
|
||||
4) Chain Inspector laufen lassen.
|
||||
5) Erwartung:
|
||||
- Edge erscheint trotzdem als outgoing/incoming
|
||||
- evidence zeigt auf den Note-Level Block
|
||||
- ideal: `scope: note`
|
||||
|
||||
Wenn das FAIL ist → klarer 0.5.0 Task.
|
||||
|
||||
---
|
||||
|
||||
## 10) Implikationen für 0.5.x / 0.6.x (wohin sinnvoll weiter)
|
||||
|
||||
### 0.5.x (Stabilisierung)
|
||||
- Note-level edge scope finalisieren (inkl. Report-Transparenz)
|
||||
- policies (analysis_policies) als zentrale Noise-Steuerung weiter ausbauen
|
||||
- Debug/Explainability weiter verbessern (effectiveRequiredLinks pro Match explizit ausgeben)
|
||||
|
||||
### 0.6.x (UX & Workflows)
|
||||
- Actionable Findings: “Was genau soll ich ändern?” inkl. Vorschlagtext oder Snippet
|
||||
- UI-Toggles: Strict/Soft, Candidates on/off
|
||||
- Template Authoring Tools: Linter, “Warum kein Match?”
|
||||
|
||||
---
|
||||
|
||||
## 11) Kurzes “Was heißt das für Mindnet im Alltag?”
|
||||
- Im Discovery-Modus: Mindnet ist ein **Explorationswerkzeug** (Hypothesen + Hinweise, wenig Warnungen).
|
||||
- Im Decisioning-Modus: Mindnet ist ein **Qualitätsprüfer** (strict, wenige false positives).
|
||||
- Der nächste große Hebel ist Note-scope: Damit wird Pflege einfacher und Ketten werden “wartbarer”.
|
||||
|
||||
---
|
||||
|
||||
## 12) Appendix: Beispielhafte Report-Signale (Interpretationshilfe)
|
||||
|
||||
- `findings: []` + `confidence: confirmed`
|
||||
→ Template passt sauber (Slots + Links vollständig im gewählten Modus).
|
||||
|
||||
- `linksComplete=false` aber `required_links=false` und **kein** `missing_link_constraints`
|
||||
→ Soft Mode: bewusst kein “Warn-Noise”, aber Transparenz bleibt.
|
||||
|
||||
- `no_causal_roles`
|
||||
→ Edges existieren, aber keine davon wird als “causal” interpretiert (Mapping oder rawEdgeType Problem).
|
||||
|
||||
- `edgesUnmapped > 0`
|
||||
→ chain_roles unvollständig oder Edge-Typ ist neu/fehlerhaft geschrieben.
|
||||
|
||||
- `effectiveIncoming=0` bei includeCandidates=false, aber incoming candidate-edge existiert
|
||||
→ Filter funktioniert wie geplant.
|
||||
|
||||
---
|
||||
|
||||
ENDE
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
doc_type: concept
|
||||
audience: architect, product_owner
|
||||
scope: ai, router, personas, resilience, agentic_rag, moe, lazy_prompts
|
||||
scope: ai, router, personas, resilience
|
||||
status: active
|
||||
version: 3.1.1
|
||||
context: "Fachkonzept der hybriden KI-Persönlichkeit, Agentic Multi-Stream RAG, WP-25a Mixture of Experts (MoE), WP-25b Lazy-Prompt-Orchestration, Provider-Kaskade und kognitiven Resilienz (Deep Fallback)."
|
||||
version: 2.8.1
|
||||
context: "Fachkonzept der hybriden KI-Persönlichkeit, der Provider-Kaskade und der kognitiven Resilienz (Deep Fallback)."
|
||||
---
|
||||
|
||||
# Konzept: KI-Persönlichkeit & Router
|
||||
|
|
@ -13,45 +13,13 @@ context: "Fachkonzept der hybriden KI-Persönlichkeit, Agentic Multi-Stream RAG,
|
|||
|
||||
Mindnet soll nicht wie eine Suchmaschine wirken, sondern wie ein **Digitaler Zwilling**. Dazu muss das System erkennen, **was** der Nutzer will, und seine „Persönlichkeit“ sowie seine technische Infrastruktur dynamisch anpassen.
|
||||
|
||||
## 1. Der Hybrid Router & Agentic Multi-Stream RAG (Das Gehirn)
|
||||
## 1. Der Hybrid Router (Das Gehirn)
|
||||
|
||||
Jede Eingabe durchläuft den **Hybrid Router**. Seit WP-25 agiert das System als **Agentic Orchestrator**, der Nutzeranfragen analysiert, in parallele Wissens-Streams aufteilt und diese zu einer kontextreichen, wertebasierten Antwort synthetisiert.
|
||||
Jede Eingabe durchläuft den **Hybrid Router**. Er entscheidet über die fachliche Strategie und die technische Ausführung.
|
||||
|
||||
### Intent-basiertes Routing (WP-25)
|
||||
|
||||
Der Router nutzt einen **Hybrid-Modus** mit Keyword Fast-Path und LLM Slow-Path:
|
||||
|
||||
**Keyword Fast-Path:**
|
||||
* Sofortige Erkennung von Triggern wie "Soll ich", "Wann", "Was ist"
|
||||
* Reduziert Latenz durch schnelle Keyword-Erkennung ohne LLM-Call
|
||||
|
||||
**LLM Slow-Path:**
|
||||
* Komplexe semantische Analyse für unklare Anfragen
|
||||
* Nutzt `intent_router_v1` Prompt zur Klassifizierung
|
||||
|
||||
**Strategien:**
|
||||
* **FACT_WHAT/FACT_WHEN:** Wissensabfrage (Wissen/Listen, Zeitpunkte)
|
||||
* **DECISION:** Beratung (Rat, Strategie, Abwägung)
|
||||
* **EMPATHY:** Reflexion (Emotionale Resonanz)
|
||||
* **CODING:** Technik (Programmierung, Syntax)
|
||||
* **INTERVIEW:** Datenerfassung (Wissen speichern)
|
||||
|
||||
### Modus A: Agentic Multi-Stream RAG (WP-25)
|
||||
|
||||
Anstelle einer einzelnen Suche führt das System **parallele Abfragen** in spezialisierten Wissens-Streams aus:
|
||||
|
||||
**Stream-Library:**
|
||||
* **Values Stream:** Identität, Ethik und Prinzipien (`value`, `principle`, `belief`, `trait`, `boundary`, `need`, `motivation`)
|
||||
* **Facts Stream:** Operative Daten (`project`, `decision`, `task`, `goal`, `event`, `state`)
|
||||
* **Biography Stream:** Persönliche Erfahrungen (`experience`, `journal`, `profile`, `person`)
|
||||
* **Risk Stream:** Hindernisse und Gefahren (`risk`, `obstacle`, `bias`)
|
||||
* **Tech Stream:** Technisches Wissen (`concept`, `source`, `glossary`, `idea`, `insight`, `skill`, `habit`)
|
||||
|
||||
**Wissens-Synthese:**
|
||||
Die Zusammenführung erfolgt über spezialisierte Templates mit expliziten Stream-Variablen (z.B. `{values_stream}`, `{risk_stream}`). Dies ermöglicht dem LLM eine differenzierte Abwägung zwischen Fakten und persönlichen Werten.
|
||||
|
||||
**Stream-Tracing:**
|
||||
Jeder Treffer wird mit `stream_origin` markiert, um Feedback-Optimierung pro Wissensbereich zu ermöglichen.
|
||||
### Modus A: RAG (Retrieval Augmented Generation)
|
||||
* **Intent:** Der Nutzer hat eine Frage oder ein Problem (`FACT`, `DECISION`, `EMPATHY`).
|
||||
* **Aktion:** Das System sucht im Gedächtnis und generiert eine Antwort.
|
||||
|
||||
### Modus B: Interview (Knowledge Capture)
|
||||
* **Intent:** Der Nutzer will Wissen speichern (`INTERVIEW`).
|
||||
|
|
@ -59,45 +27,13 @@ Jeder Treffer wird mit `stream_origin` markiert, um Feedback-Optimierung pro Wis
|
|||
|
||||
---
|
||||
|
||||
## 2. Mixture of Experts (MoE) Architektur (WP-25a)
|
||||
## 2. Die hybride LLM-Landschaft (Resilienz-Kaskade)
|
||||
|
||||
Seit WP-25a nutzt MindNet eine **profilbasierte Experten-Steuerung** statt einer globalen Provider-Konfiguration. Jede Systemaufgabe wird einem dedizierten Profil zugewiesen, das Modell, Provider und Parameter unabhängig definiert.
|
||||
Ein intelligenter Zwilling muss jederzeit verfügbar sein. Mindnet v2.8.1 nutzt eine **dreistufige Kaskade**, um Intelligenz, Kosten und Verfügbarkeit zu optimieren:
|
||||
|
||||
### 2.1 Experten-Profile
|
||||
|
||||
**Zentrale Registry (`llm_profiles.yaml`):**
|
||||
* **`synthesis_pro`:** Hochwertige Synthese für Chat-Antworten (Cloud)
|
||||
* **`tech_expert`:** Fachspezialist für Code & Technik (Claude 3.5 Sonnet)
|
||||
* **`compression_fast`:** Schnelle Kompression & Routing (Mistral 7B)
|
||||
* **`ingest_validator`:** Deterministische Validierung (Temperature 0.0)
|
||||
* **`identity_safe`:** Lokaler Anker (Ollama/Phi-3) für maximale Privacy
|
||||
|
||||
**Vorteile:**
|
||||
* **Aufgabenspezifische Optimierung:** Jede Aufgabe nutzt das optimale Modell
|
||||
* **Hardware-Optimierung:** Lokaler Anker für kleine Hardware-Umgebungen
|
||||
* **Wartbarkeit:** Zentrale Konfiguration statt verstreuter ENV-Variablen
|
||||
|
||||
### 2.2 Rekursive Fallback-Kaskade
|
||||
|
||||
Die Profile implementieren eine **automatische Fallback-Logik**:
|
||||
|
||||
1. **Primäres Profil:** System versucht das angeforderte Profil (z.B. `synthesis_pro`)
|
||||
2. **Fallback-Level 1:** Bei Fehler → `fallback_profile` (z.B. `synthesis_backup`)
|
||||
3. **Fallback-Level 2:** Bei weiterem Fehler → nächster Fallback (z.B. `identity_safe`)
|
||||
4. **Terminaler Endpunkt:** `identity_safe` hat keinen Fallback (lokales Modell als letzte Instanz)
|
||||
|
||||
**Schutzmechanismen:**
|
||||
* **Zirkuläre Referenzen:** `visited_profiles`-Tracking verhindert Endlosschleifen
|
||||
* **Background-Semaphore:** Parallele Tasks werden gedrosselt
|
||||
|
||||
### 2.3 Die hybride LLM-Landschaft (Legacy & MoE)
|
||||
|
||||
Ein intelligenter Zwilling muss jederzeit verfügbar sein. Seit WP-25a wird die Resilienz durch die **MoE Fallback-Kaskade** gewährleistet:
|
||||
|
||||
1. **Stufe 1: Cloud-Experten:** Spezialisierte Profile für verschiedene Aufgaben (z.B. `synthesis_pro`, `tech_expert`)
|
||||
1. **Stufe 1: Cloud-Speed (Turbo-Mode):** Primäre Wahl für komplexe Extraktionsaufgaben und schnelle RAG-Antworten mittels OpenRouter (Mistral-7B) oder Google Gemini (2.5-flash-lite).
|
||||
2. **Stufe 2: Quoten-Resilienz:** Erkennt das System eine Drosselung durch Cloud-Provider (HTTP 429), pausiert es kontrolliert (`LLM_RATE_LIMIT_WAIT`), führt automatisierte Retries durch und schützt so den laufenden Prozess.
|
||||
3. **Stufe 3: Lokale Souveränität (Ollama):**
|
||||
* **Technischer Fallback:** Schlagen alle Cloud-Versuche fehl, übernimmt das lokale Modell (Phi-3) via `identity_safe` Profil.
|
||||
3. **Stufe 3: Deep Fallback & lokale Souveränität (Ollama):** * **Technischer Fallback:** Schlagen alle Cloud-Versuche fehl, übernimmt das lokale Modell (Phi-3).
|
||||
* **Kognitiver Fallback (v2.11.14):** Liefert die Cloud zwar technisch eine Antwort, verweigert aber inhaltlich die Verarbeitung (Silent Refusal/Policy Violation), wird ein **Deep Fallback** erzwungen, um die Datenintegrität lokal zu retten.
|
||||
|
||||
|
||||
|
|
@ -109,22 +45,18 @@ Ein intelligenter Zwilling muss jederzeit verfügbar sein. Seit WP-25a wird die
|
|||
Mindnet wechselt den Hut, je nach Situation.
|
||||
|
||||
### 3.1 Der Berater (Strategy: DECISION)
|
||||
* **Auslöser:** Fragen wie „Soll ich...?", „Was ist besser?", „Empfehlung...".
|
||||
* **Multi-Stream Retrieval (WP-25):** Führt parallele Abfragen in Values Stream, Facts Stream und Risk Stream aus.
|
||||
* **Wissens-Synthese:** Wägt Fakten gegen Werte ab, evaluiert Risiken und prüft Kompatibilität mit langfristiger Identität.
|
||||
* **Reasoning:** *„Wäge die Fakten gegen meine Werte ab. Sei strikt bei Risiken."*
|
||||
* **Auslöser:** Fragen wie „Soll ich...?“, „Was ist besser?“.
|
||||
* **Strategic Retrieval:** Lädt aktiv Notizen der Typen `value` (Werte), `goal` (Ziele) und `risk` (Risiken), auch wenn sie im Text nicht direkt vorkommen.
|
||||
* **Reasoning:** *„Wäge die Fakten gegen meine Werte ab. Sei strikt bei Risiken.“*
|
||||
|
||||
### 3.2 Der Spiegel (Strategy: EMPATHY)
|
||||
* **Auslöser:** Emotionale Aussagen („Ich bin frustriert", „Ich fühle...", „Stress...").
|
||||
* **Multi-Stream Retrieval (WP-25):** Führt parallele Abfragen in Biography Stream und Values Stream aus.
|
||||
* **Wissens-Synthese:** Greift auf persönliche Erfahrungen und Werte zurück, um emotionale Resonanz zu schaffen.
|
||||
* **Reasoning:** *„Nutze meine eigenen Erfahrungen, um die Situation einzuordnen."*
|
||||
* **Auslöser:** Emotionale Aussagen („Ich bin frustriert“).
|
||||
* **Strategic Retrieval:** Lädt `experience` (Erfahrungen) und `belief` (Glaubenssätze).
|
||||
* **Reasoning:** *„Nutze meine eigenen Erfahrungen, um die Situation einzuordnen.“*
|
||||
|
||||
### 3.3 Der Bibliothekar (Strategy: FACT_WHAT / FACT_WHEN)
|
||||
* **Auslöser:** Sachfragen („Was ist...?", „Welche sind...?", „Wann...?", „Datum...").
|
||||
* **Multi-Stream Retrieval (WP-25):** Führt parallele Abfragen in Facts Stream, Tech Stream und Biography Stream aus.
|
||||
* **Wissens-Synthese:** Kombiniert harte Fakten mit persönlichen Erfahrungen, falls vorhanden.
|
||||
* **Behavior:** Präzise, neutral, strukturiert.
|
||||
### 3.3 Der Bibliothekar (Strategy: FACT)
|
||||
* **Auslöser:** Sachfragen („Was ist Qdrant?“).
|
||||
* **Behavior:** Präzise, neutral, kurz.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,456 +0,0 @@
|
|||
---
|
||||
doc_type: concept
|
||||
audience: architect, developer
|
||||
scope: architecture, design_patterns, modularization
|
||||
status: active
|
||||
version: 2.9.1
|
||||
context: "Architektur-Patterns, Design-Entscheidungen und modulare Struktur von Mindnet. Basis für Verständnis und Erweiterungen."
|
||||
---
|
||||
|
||||
# Architektur-Patterns & Design-Entscheidungen
|
||||
|
||||
Dieses Dokument beschreibt die zentralen Architektur-Patterns und Design-Entscheidungen, die Mindnet prägen. Es dient als Referenz für Entwickler und Architekten, um das System zu verstehen und konsistent zu erweitern.
|
||||
|
||||
## 1. Kern-Paradigmen
|
||||
|
||||
### 1.1 Filesystem First (Source of Truth)
|
||||
|
||||
**Prinzip:** Markdown-Dateien im Vault sind die einzige Quelle der Wahrheit. Die Datenbank (Qdrant) ist ein abgeleiteter Index.
|
||||
|
||||
**Implikationen:**
|
||||
- Dateien werden immer direkt von der Festplatte gelesen (z.B. im Editor)
|
||||
- Datenbank-Inhalte können aus Markdown rekonstruiert werden
|
||||
- Änderungen erfolgen primär im Vault, nicht in der DB
|
||||
- Die Datenbank ist ein Cache, kein primärer Speicher
|
||||
|
||||
**Code-Beispiele:**
|
||||
- `ui_callbacks.py`: Liest Dateien direkt von Disk
|
||||
- `ingestion_processor.py`: Schreibt zuerst auf Disk, dann in DB
|
||||
|
||||
### 1.2 Late Binding (Späte Semantik)
|
||||
|
||||
**Prinzip:** Struktur und Interpretation werden in Konfigurationen definiert, nicht im Code.
|
||||
|
||||
**Implikationen:**
|
||||
- Neue Note-Typen werden in `types.yaml` definiert, nicht im Code
|
||||
- Prompt-Templates sind in `prompts.yaml` konfigurierbar
|
||||
- Edge-Typen werden in `edge_vocabulary.md` verwaltet
|
||||
- Die "Persönlichkeit" ist Config, kein Code
|
||||
|
||||
**Vorteile:**
|
||||
- Erweiterbarkeit ohne Code-Änderungen
|
||||
- A/B-Testing von Prompt-Strategien
|
||||
- Anpassung an verschiedene Use-Cases
|
||||
|
||||
### 1.3 Virtual Schema Layer
|
||||
|
||||
**Prinzip:** Markdown-Dateien benötigen nur minimale Frontmatter-Angaben. Komplexität wird zur Laufzeit injiziert.
|
||||
|
||||
**Beispiel:**
|
||||
```yaml
|
||||
# Minimal im Frontmatter
|
||||
---
|
||||
id: 20250101-test
|
||||
title: Test
|
||||
type: project
|
||||
---
|
||||
|
||||
# Im Code wird zur Laufzeit ergänzt:
|
||||
# - chunking_profile aus types.yaml
|
||||
# - retriever_weight aus types.yaml
|
||||
# - edge_defaults aus types.yaml
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Robuste Markdown-Dateien (weniger Breaking Changes)
|
||||
- Zentrale Verwaltung von Logik
|
||||
- Einfache Migration zwischen Versionen
|
||||
|
||||
---
|
||||
|
||||
## 2. Modulare Architektur (WP-14)
|
||||
|
||||
### 2.1 Paket-Struktur
|
||||
|
||||
Seit WP-14 ist die Core-Logik in spezialisierte Pakete unterteilt:
|
||||
|
||||
```
|
||||
app/core/
|
||||
├── chunking/ # Text-Segmentierung
|
||||
│ ├── chunking_strategies.py # Sliding/Heading-Strategien
|
||||
│ ├── chunking_processor.py # Orchestrierung
|
||||
│ └── chunking_propagation.py # Edge-Vererbung
|
||||
├── database/ # Qdrant-Infrastruktur
|
||||
│ ├── qdrant.py # Client & Config
|
||||
│ └── qdrant_points.py # Point-Mapping
|
||||
├── graph/ # Graph-Logik
|
||||
│ ├── graph_subgraph.py # Expansion
|
||||
│ └── graph_weights.py # Scoring
|
||||
├── ingestion/ # Import-Pipeline
|
||||
│ ├── ingestion_processor.py # Two-Pass Workflow
|
||||
│ └── ingestion_validation.py # Mistral-safe Parsing
|
||||
├── parser/ # Markdown-Parsing
|
||||
│ ├── parsing_markdown.py # Frontmatter/Body
|
||||
│ └── parsing_scanner.py # File-Scan
|
||||
└── retrieval/ # Suche & Scoring
|
||||
├── retriever.py # Orchestrator
|
||||
└── retriever_scoring.py # Mathematik
|
||||
```
|
||||
|
||||
### 2.2 Design-Pattern: Proxy-Adapter (Abwärtskompatibilität)
|
||||
|
||||
**Problem:** Nach WP-14 Modularisierung müssen alte Import-Pfade weiter funktionieren.
|
||||
|
||||
**Lösung:** Proxy-Module leiten Anfragen an neue Pakete weiter.
|
||||
|
||||
**Beispiel:**
|
||||
```python
|
||||
# app/core/retriever.py (Proxy)
|
||||
from .retrieval.retriever import (
|
||||
Retriever, hybrid_retrieve, semantic_retrieve
|
||||
)
|
||||
__all__ = ["Retriever", "hybrid_retrieve", "semantic_retrieve"]
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Keine Breaking Changes für bestehenden Code
|
||||
- Graduelle Migration möglich
|
||||
- Alte Scripts funktionieren weiter
|
||||
|
||||
### 2.3 Design-Pattern: Singleton (Edge Registry)
|
||||
|
||||
**Problem:** Edge Registry muss konsistent über alle Services sein.
|
||||
|
||||
**Lösung:** Singleton-Pattern mit Lazy Loading.
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
# app/services/edge_registry.py
|
||||
class EdgeRegistry:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Einheitliche Validierung
|
||||
- Keine Duplikation von Vokabular-Daten
|
||||
- Thread-safe Initialisierung
|
||||
|
||||
---
|
||||
|
||||
## 3. Asynchrone Verarbeitung
|
||||
|
||||
### 3.1 Background Tasks Pattern
|
||||
|
||||
**Problem:** Ingestion kann lange dauern (LLM-Calls, Embeddings). Blockiert API nicht.
|
||||
|
||||
**Lösung:** FastAPI Background Tasks für non-blocking Verarbeitung.
|
||||
|
||||
**Flow:**
|
||||
1. API empfängt Request (`/ingest/save`)
|
||||
2. Datei wird sofort auf Disk geschrieben
|
||||
3. API antwortet mit `status: "queued"`
|
||||
4. Background Task startet Ingestion asynchron
|
||||
5. User kann weiterarbeiten
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
@router.post("/save")
|
||||
async def save_note(req: SaveRequest, background_tasks: BackgroundTasks):
|
||||
# Sofortige Persistenz
|
||||
write_to_disk(req.markdown_content)
|
||||
|
||||
# Async Processing
|
||||
background_tasks.add_task(run_ingestion_task, ...)
|
||||
|
||||
return SaveResponse(status="queued", ...)
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Keine Timeouts bei großen Dateien
|
||||
- Bessere User Experience
|
||||
- System bleibt responsiv
|
||||
|
||||
### 3.2 Traffic Control (Semaphore Pattern)
|
||||
|
||||
**Problem:** Parallele LLM-Calls können System überlasten.
|
||||
|
||||
**Lösung:** Semaphore begrenzt parallele Hintergrund-Tasks.
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
# app/services/llm_service.py
|
||||
background_semaphore = asyncio.Semaphore(
|
||||
settings.BACKGROUND_LIMIT # Default: 2
|
||||
)
|
||||
|
||||
async def generate(...):
|
||||
if priority == "background":
|
||||
async with background_semaphore:
|
||||
return await _call_llm(...)
|
||||
else: # realtime
|
||||
return await _call_llm(...) # Keine Limitierung
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Schutz vor API-Quoten-Überschreitung
|
||||
- Stabiler Betrieb bei hoher Last
|
||||
- Priorisierung von Echtzeit-Anfragen
|
||||
|
||||
---
|
||||
|
||||
## 4. Resilienz-Patterns
|
||||
|
||||
### 4.1 Provider-Kaskade (Fallback-Kette)
|
||||
|
||||
**Problem:** Cloud-Provider können ausfallen oder Quoten überschreiten.
|
||||
|
||||
**Lösung:** Dreistufige Kaskade mit intelligentem Fallback.
|
||||
|
||||
**Stufen:**
|
||||
1. **Cloud (OpenRouter/Gemini):** Schnell, aber abhängig von Provider
|
||||
2. **Rate-Limit-Handling:** Automatische Retries bei HTTP 429
|
||||
3. **Lokaler Fallback (Ollama):** Langsam, aber immer verfügbar
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
# app/services/llm_service.py
|
||||
async def generate(...):
|
||||
try:
|
||||
return await _call_cloud(...)
|
||||
except RateLimitError:
|
||||
await asyncio.sleep(LLM_RATE_LIMIT_WAIT)
|
||||
return await _call_cloud(...) # Retry
|
||||
except Exception:
|
||||
return await _call_ollama(...) # Fallback
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Hohe Verfügbarkeit
|
||||
- Kostenoptimierung (Cloud für Speed, Lokal für Fallback)
|
||||
- Resilienz gegen Provider-Ausfälle
|
||||
|
||||
### 4.2 Deep Fallback (Kognitiver Fallback)
|
||||
|
||||
**Problem:** Cloud liefert technisch erfolgreiche, aber inhaltlich leere Antworten (Silent Refusal).
|
||||
|
||||
**Lösung:** Validierung der Antwort-Qualität, nicht nur HTTP-Status.
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
response = await _call_cloud(...)
|
||||
if is_valid_json(response):
|
||||
return response
|
||||
else:
|
||||
# Deep Fallback: Cloud hat blockiert, lokaler Fallback
|
||||
return await _call_ollama(...)
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Erkennung von Policy-Violations
|
||||
- Datenintegrität trotz Cloud-Filter
|
||||
- Lokale Souveränität
|
||||
|
||||
---
|
||||
|
||||
## 5. Datenfluss-Patterns
|
||||
|
||||
### 5.1 Two-Pass Workflow (WP-15b)
|
||||
|
||||
**Problem:** Smart Edge Validation benötigt globalen Kontext (alle Notizen).
|
||||
|
||||
**Lösung:** Zwei-Phasen-Import mit Pre-Scan.
|
||||
|
||||
**Pass 1: Pre-Scan**
|
||||
- Schnelles Scannen aller Dateien
|
||||
- Aufbau von `LocalBatchCache` (IDs, Titel, Summaries)
|
||||
- Keine LLM-Calls
|
||||
|
||||
**Pass 2: Semantic Processing**
|
||||
- Nur für geänderte Dateien
|
||||
- Binäre Validierung gegen Cache
|
||||
- Effiziente Nutzung von LLM-Quoten
|
||||
|
||||
**Vorteile:**
|
||||
- Reduktion von LLM-Calls (nur Validierung, keine Extraktion)
|
||||
- Konsistente Validierung (globaler Kontext)
|
||||
- Schnellerer Import
|
||||
|
||||
### 5.2 Idempotenz-Pattern
|
||||
|
||||
**Prinzip:** Mehrfache Imports derselben Datei führen zum gleichen Ergebnis.
|
||||
|
||||
**Mechanismen:**
|
||||
- **Deterministische IDs:** UUIDv5 basierend auf Datei-Inhalt
|
||||
- **Hash-basierte Change Detection:** Multi-Hash für `body` und `full`
|
||||
- **Deduplizierung:** Kanten werden anhand Identität erkannt
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
# Deterministische ID
|
||||
note_id = uuid.uuid5(NAMESPACE_URL, f"{file_path}#{content_hash}")
|
||||
|
||||
# Change Detection
|
||||
if current_hash == stored_hash:
|
||||
skip_processing() # Idempotent
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Sicherheit bei Re-Imports
|
||||
- Keine Duplikate
|
||||
- Konsistente Datenbank
|
||||
|
||||
---
|
||||
|
||||
## 6. Frontend-Patterns
|
||||
|
||||
### 6.1 Active Inspector Pattern
|
||||
|
||||
**Problem:** Graph-Re-Renders bei Knoten-Selektion führen zu Flackern.
|
||||
|
||||
**Lösung:** CSS-Klassen statt State-Änderungen für Selektion.
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
# ui_graph_cytoscape.py
|
||||
stylesheet = [
|
||||
{
|
||||
"selector": ".inspected",
|
||||
"style": {"border-width": 6, "border-color": "#FFC300"}
|
||||
}
|
||||
]
|
||||
# Selektion ändert nur CSS-Klasse, nicht React-Key
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Stabiles UI (kein Re-Render)
|
||||
- Bessere Performance
|
||||
- Smooth User Experience
|
||||
|
||||
### 6.2 Resurrection Pattern
|
||||
|
||||
**Problem:** Streamlit vergisst Eingaben bei Re-Runs.
|
||||
|
||||
**Lösung:** Aggressive Synchronisation in `session_state`.
|
||||
|
||||
**Code:**
|
||||
```python
|
||||
# ui_editor.py
|
||||
if widget_key not in st.session_state:
|
||||
st.session_state[widget_key] = restore_from_data_key()
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Texteingaben überleben Tab-Wechsel
|
||||
- Keine Datenverluste
|
||||
- Bessere UX
|
||||
|
||||
---
|
||||
|
||||
## 7. Erweiterbarkeit: "Teach-the-AI" Paradigma
|
||||
|
||||
Mindnet lernt durch **Konfiguration**, nicht durch Training.
|
||||
|
||||
### 7.1 Drei-Ebenen-Erweiterung
|
||||
|
||||
**1. Daten-Ebene (`types.yaml`):**
|
||||
```yaml
|
||||
risk:
|
||||
retriever_weight: 0.90
|
||||
edge_defaults: ["blocks"]
|
||||
```
|
||||
|
||||
**2. Strategie-Ebene (`decision_engine.yaml`):**
|
||||
```yaml
|
||||
DECISION:
|
||||
inject_types: ["value", "risk"]
|
||||
```
|
||||
|
||||
**3. Kognitive Ebene (`prompts.yaml`):**
|
||||
```yaml
|
||||
risk_definition:
|
||||
ollama: "Ein Risiko ist eine potenzielle Gefahr..."
|
||||
```
|
||||
|
||||
**Vorteile:**
|
||||
- Keine Code-Änderungen nötig
|
||||
- Schnelle Iteration
|
||||
- A/B-Testing möglich
|
||||
|
||||
---
|
||||
|
||||
## 8. Design-Entscheidungen & Trade-offs
|
||||
|
||||
### 8.1 Qdrant als Vektor-DB
|
||||
|
||||
**Entscheidung:** Qdrant statt Pinecone/Weaviate
|
||||
|
||||
**Gründe:**
|
||||
- Self-hosted (Privacy First)
|
||||
- Open Source
|
||||
- Gute Performance bei lokaler Installation
|
||||
|
||||
**Trade-off:** Mehr Wartungsaufwand als Managed Service
|
||||
|
||||
### 8.2 Hybrid Retrieval (Semantik + Graph)
|
||||
|
||||
**Entscheidung:** Kombination von Vektor-Suche und Graph-Expansion
|
||||
|
||||
**Gründe:**
|
||||
- Semantik findet ähnliche Inhalte
|
||||
- Graph findet strukturelle Verbindungen
|
||||
- Kombination liefert bessere Relevanz
|
||||
|
||||
**Trade-off:** Höhere Komplexität, mehr Berechnungsaufwand
|
||||
|
||||
### 8.3 Background Tasks statt Queue-System
|
||||
|
||||
**Entscheidung:** FastAPI Background Tasks statt Redis/RabbitMQ
|
||||
|
||||
**Gründe:**
|
||||
- Einfacher Setup (keine zusätzliche Infrastruktur)
|
||||
- Ausreichend für Single-User-Szenario
|
||||
- Weniger Moving Parts
|
||||
|
||||
**Trade-off:** Keine Persistenz bei Server-Neustart (Tasks gehen verloren)
|
||||
|
||||
---
|
||||
|
||||
## 9. Konsistenz-Garantien
|
||||
|
||||
### 9.1 Deterministische IDs
|
||||
|
||||
**Prinzip:** UUIDv5 basierend auf Datei-Inhalt
|
||||
|
||||
**Vorteile:**
|
||||
- Reproduzierbare IDs
|
||||
- Keine Duplikate bei Re-Import
|
||||
- Graph ist aus Markdown rekonstruierbar
|
||||
|
||||
### 9.2 Provenance-Hierarchie
|
||||
|
||||
**Prinzip:** Kanten haben Qualitätsstufen (explicit > smart > rule)
|
||||
|
||||
**Vorteile:**
|
||||
- Menschliche Intention hat Vorrang
|
||||
- System-Heuristiken können überschrieben werden
|
||||
- Transparente Gewichtung
|
||||
|
||||
---
|
||||
|
||||
## 10. Weitere Informationen
|
||||
|
||||
- **Vision & Strategie:** Siehe [Vision & Strategie](../00_General/00_vision_and_strategy.md)
|
||||
- **Graph-Logik:** Siehe [Graph-Logik](02_concept_graph_logic.md)
|
||||
- **KI-Persönlichkeit:** Siehe [KI-Persönlichkeit](02_concept_ai_personality.md)
|
||||
- **Developer Guide:** Siehe [Developer Guide](../05_Development/05_developer_guide.md)
|
||||
|
||||
---
|
||||
|
||||
**Letzte Aktualisierung:** 2025-01-XX
|
||||
**Version:** 2.9.1
|
||||
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
doc_type: concept
|
||||
audience: architect, product_owner
|
||||
scope: graph, logic, provenance, agentic_validation, note_scope
|
||||
scope: graph, logic, provenance
|
||||
status: active
|
||||
version: 4.5.8
|
||||
context: "Fachliche Beschreibung des Wissensgraphen: Knoten, Kanten, Provenance, Matrix-Logik, WP-15c Multigraph-Support, WP-22 Scoring-Prinzipien, WP-24c Phase 3 Agentic Edge Validation und automatische Spiegelkanten."
|
||||
version: 2.7.0
|
||||
context: "Fachliche Beschreibung des Wissensgraphen: Knoten, Kanten, Provenance, Matrix-Logik und WP-22 Scoring-Prinzipien."
|
||||
---
|
||||
|
||||
# Konzept: Die Graph-Logik
|
||||
|
|
@ -59,7 +59,7 @@ Um eine konsistente mathematische Gewichtung zu garantieren, werden alle Kanten
|
|||
|
||||
### 2.2 Provenance (Herkunft & Vertrauen)
|
||||
|
||||
Nicht alle Kanten sind gleich viel wert. Mindnet unterscheidet mehrere Qualitätsstufen (**Provenance**), um bei der Berechnung des Edge-Bonus Prioritäten zu setzen.
|
||||
Nicht alle Kanten sind gleich viel wert. Mindnet unterscheidet drei Qualitätsstufen (**Provenance**), um bei der Berechnung des Edge-Bonus Prioritäten zu setzen.
|
||||
|
||||
**1. Explicit (Der Mensch hat es gesagt)**
|
||||
* *Quelle:* Inline-Links (`[[rel:...]]`) oder Wikilinks im Text.
|
||||
|
|
@ -76,19 +76,6 @@ Nicht alle Kanten sind gleich viel wert. Mindnet unterscheidet mehrere Qualität
|
|||
* *Vertrauen:* **Niedrig (0.7)**.
|
||||
* *Bedeutung:* Systemseitige Heuristik. Diese Verbindungen dienen der Entdeckung neuer Pfade, haben aber weniger Gewicht als explizite Links.
|
||||
|
||||
**4. Structure (System-interne Verkettung)**
|
||||
* *Quelle:* Automatische Struktur-Kanten (`belongs_to`, `next`, `prev`).
|
||||
* *Vertrauen:* **Hoch (1.0)**.
|
||||
* *Bedeutung:* Diese Kanten werden ausschließlich durch interne Prozesse erzeugt und sind durch die **Provenance Firewall** geschützt. Sie können weder durch Nutzer noch durch KI manipuliert werden.
|
||||
|
||||
### 2.3 Provenance Firewall (WP-15c)
|
||||
|
||||
Die **Edge Registry** (v0.8.0) implementiert eine strikte Trennung zwischen System-Kanten und Inhalts-Kanten:
|
||||
|
||||
* **Geschützte System-Kanten:** `next`, `prev`, `belongs_to` dürfen nur mit `provenance="structure"` gesetzt werden.
|
||||
* **Blockierung:** Alle anderen Provenienzen (`explicit`, `semantic_ai`, `inherited`, `global_pool`, `rule`) werden bei System-Kanten blockiert und auf `related_to` zurückgesetzt.
|
||||
* **Zweck:** Sichert die Graph-Integrität und verhindert Manipulationen an der internen Struktur.
|
||||
|
||||
---
|
||||
|
||||
## 3. Matrix-Logik (Kontext-Sensitivität)
|
||||
|
|
@ -131,152 +118,8 @@ Der Intent-Router injiziert spezifische Multiplikatoren für kanonische Typen:
|
|||
|
||||
---
|
||||
|
||||
## 6. Section-basierte Links & Multigraph-Support
|
||||
|
||||
Seit v2.9.1 unterstützt Mindnet **Deep-Links** zu spezifischen Abschnitten innerhalb einer Note.
|
||||
|
||||
### 6.1 Link-Parsing & Self-Links (WP-15c)
|
||||
|
||||
Die Logik erkennt nun präzise **Obsidian-Anker** (`[[Note#Section]]`) und **Self-Links** (`[[#Section]]`):
|
||||
|
||||
* **Obsidian-Anker:** `[[Note#Section]]` wird in `target_id="Note"` und `target_section="Section"` aufgeteilt.
|
||||
* **Self-Links:** `[[#Section]]` wird zu `target_id="current_note_id"` und `target_section="Section"` aufgelöst.
|
||||
* **`target_id`:** Enthält nur den Note-Namen (z.B. "Mein Leitbild")
|
||||
* **`target_section`:** Enthält den Abschnitts-Namen (z.B. "P3 – Disziplin")
|
||||
|
||||
**Vorteil:** Verhindert "Phantom-Knoten", die durch das Einbeziehen des Anchors in die `target_id` entstanden wären. Ermöglicht präzise Verlinkung innerhalb derselben Note.
|
||||
|
||||
### 6.2 Multigraph-Support
|
||||
Die Edge-ID enthält nun einen `variant`-Parameter (die Section), sodass mehrere Kanten zwischen denselben Knoten existieren können, wenn sie auf verschiedene Sections zeigen:
|
||||
* `[[Note#Section1]]` → Edge-ID: `src->tgt:kind@Section1`
|
||||
* `[[Note#Section2]]` → Edge-ID: `src->tgt:kind@Section2`
|
||||
|
||||
### 6.3 Semantische Deduplizierung
|
||||
Die Deduplizierung basiert auf dem `src->tgt:kind@sec` Key, um sicherzustellen, dass identische Links (gleiche Quelle, Ziel, Typ und Section) nicht mehrfach erstellt werden.
|
||||
|
||||
---
|
||||
|
||||
## 7. Automatische Spiegelkanten (Invers-Logik) - WP-24c v4.5.8
|
||||
|
||||
Das System erzeugt automatisch **Spiegelkanten** (Invers-Kanten) für explizite Verbindungen, um die Auffindbarkeit von Informationen zu verdoppeln.
|
||||
|
||||
### 7.1 Funktionsweise
|
||||
|
||||
**Beispiel:**
|
||||
- **Explizite Kante:** Note A `depends_on: Note B`
|
||||
- **Automatische Spiegelkante:** Note B `enforced_by: Note A`
|
||||
|
||||
**Vorteil:** Beide Richtungen sind durchsuchbar. Wenn du nach "Note B" suchst, findest du auch alle Notizen, die von "Note B" abhängen (via `enforced_by`).
|
||||
|
||||
### 7.2 Invers-Mapping
|
||||
|
||||
Die Edge Registry definiert für jeden Kanten-Typ das symmetrische Gegenstück:
|
||||
- `depends_on` ↔ `enforced_by`
|
||||
- `derived_from` ↔ `resulted_in`
|
||||
- `impacts` ↔ `impacted_by`
|
||||
- `blocks` ↔ `blocked_by`
|
||||
- `next` ↔ `prev`
|
||||
- `related_to` ↔ `related_to` (symmetrisch)
|
||||
|
||||
### 7.3 Priorität & Schutz
|
||||
|
||||
* **Explizite Kanten haben Vorrang:** Wenn du bereits beide Richtungen explizit gesetzt hast, wird keine automatische Spiegelkante erzeugt (keine Duplikate)
|
||||
* **Höhere Wirksamkeit expliziter Kanten:** Explizit gesetzte Kanten haben höhere Confidence-Werte (`confidence: 1.0`) als automatisch generierte Spiegelkanten (`confidence: 0.9 * original`)
|
||||
* **Provenance Firewall:** System-Kanten (`belongs_to`, `next`, `prev`) können nicht manuell überschrieben werden
|
||||
|
||||
### 7.4 Phase 2 Symmetrie-Injektion
|
||||
|
||||
Spiegelkanten werden am Ende des gesamten Imports (Phase 2) in einem Batch-Prozess injiziert:
|
||||
- **Authority-Check:** Nur wenn keine explizite Kante existiert, wird die Spiegelkante erzeugt
|
||||
- **ID-Konsistenz:** Verwendet exakt dieselbe ID-Generierung wie Phase 1 (inkl. `target_section`)
|
||||
- **Logging:** `🔄 [SYMMETRY]` zeigt die erzeugten Spiegelkanten
|
||||
|
||||
---
|
||||
|
||||
## 8. Phase 3 Agentic Edge Validation - WP-24c v4.5.8
|
||||
|
||||
Das System implementiert ein finales Validierungs-Gate für alle Kanten mit `candidate:` Präfix, um "Geister-Verknüpfungen" zu verhindern und die Graph-Qualität zu sichern.
|
||||
|
||||
### 8.1 Trigger-Kriterium
|
||||
|
||||
Kanten erhalten `candidate:` Präfix, wenn sie:
|
||||
- In `### Unzugeordnete Kanten` Sektionen stehen
|
||||
- Von der Smart Edge Allocation als Kandidaten vorgeschlagen wurden
|
||||
- Explizit als `candidate:` markiert wurden
|
||||
|
||||
### 8.2 Validierungsprozess
|
||||
|
||||
1. **Kontext-Optimierung:**
|
||||
- **Note-Scope (`scope: note`):** LLM nutzt `note_summary` (Top 5 Chunks) oder `note_text` (aggregierter Gesamttext)
|
||||
- **Chunk-Scope (`scope: chunk`):** LLM nutzt spezifischen Chunk-Text, falls verfügbar, sonst Note-Text
|
||||
|
||||
2. **LLM-Validierung:**
|
||||
- Nutzt `ingest_validator` Profil (Temperature 0.0 für Determinismus)
|
||||
- Prüft semantisch: "Passt diese Verbindung zum Kontext?"
|
||||
- Binäre Entscheidung: YES (VERIFIED) oder NO (REJECTED)
|
||||
|
||||
3. **Ergebnis:**
|
||||
- **VERIFIED:** `candidate:` Präfix wird entfernt, Kante wird persistiert
|
||||
- **REJECTED:** Kante wird **nicht** in die Datenbank geschrieben (verhindert persistente "Geister-Verknüpfungen")
|
||||
|
||||
### 8.3 Fehlertoleranz
|
||||
|
||||
Das System unterscheidet zwischen:
|
||||
- **Transienten Fehlern (Netzwerk, Timeout):** Kante wird erlaubt (Integrität vor Präzision)
|
||||
- **Permanenten Fehlern (Config, Validation):** Kante wird abgelehnt (Graph-Qualität schützen)
|
||||
|
||||
### 8.4 Provenance nach Validierung
|
||||
|
||||
- **Vor Validierung:** `provenance: "candidate:global_pool"` oder `rule_id: "candidate:..."`
|
||||
- **Nach VERIFIED:** `provenance: "global_pool"` oder `rule_id: "explicit"` (Präfix entfernt)
|
||||
- **Nach REJECTED:** Kante existiert nicht im Graph (wird nicht persistiert)
|
||||
|
||||
---
|
||||
|
||||
## 9. Note-Scope vs. Chunk-Scope - WP-24c v4.2.0
|
||||
|
||||
Das System unterscheidet zwischen **Note-Scope** (globale Verbindungen) und **Chunk-Scope** (lokale Referenzen).
|
||||
|
||||
### 9.1 Chunk-Scope (Standard)
|
||||
|
||||
- **Quelle:** `source_id = chunk_id` (z.B. `note-id#c00`)
|
||||
- **Kontext:** Spezifischer Textabschnitt (Chunk)
|
||||
- **Verwendung:** Lokale Referenzen innerhalb eines Abschnitts
|
||||
- **Phase 3 Validierung:** Nutzt spezifischen Chunk-Text
|
||||
|
||||
**Beispiel:**
|
||||
```markdown
|
||||
In diesem Abschnitt nutzen wir [[rel:uses|Technologie X]].
|
||||
```
|
||||
|
||||
### 9.2 Note-Scope
|
||||
|
||||
- **Quelle:** `source_id = note_id` (nicht `chunk_id`)
|
||||
- **Kontext:** Gesamte Note (Note-Summary oder Note-Text)
|
||||
- **Verwendung:** Globale Verbindungen, die für die ganze Note gelten
|
||||
- **Phase 3 Validierung:** Nutzt `note_summary` (Top 5 Chunks) oder `note_text` (aggregierter Gesamttext)
|
||||
|
||||
**Beispiel:**
|
||||
```markdown
|
||||
## Smart Edges
|
||||
|
||||
[[rel:depends_on|Projekt-Übersicht]]
|
||||
[[rel:part_of|Größeres System]]
|
||||
```
|
||||
|
||||
### 9.3 Priorität
|
||||
|
||||
Bei Duplikaten (gleiche Kante in Chunk-Scope und Note-Scope):
|
||||
1. **Note-Scope Links** haben **höchste Priorität**
|
||||
2. Dann Confidence-Wert
|
||||
3. Dann Provenance-Priority
|
||||
|
||||
---
|
||||
|
||||
## 10. Idempotenz & Konsistenz
|
||||
## 6. Idempotenz & Konsistenz
|
||||
|
||||
Das System garantiert fachliche Konsistenz auch bei mehrfachen Importen.
|
||||
* **Stabile IDs:** Deterministische IDs verhindern Duplikate bei Re-Imports.
|
||||
* **Deduplizierung:** Kanten werden anhand ihrer Identität (inkl. Section) erkannt. Die "stärkere" Provenance gewinnt.
|
||||
* **Format-agnostische Erkennung:** Kanten werden unabhängig vom Format (Inline, Callout, Wikilink) erkannt, um Dopplungen zu vermeiden.
|
||||
* **Phase 3 Validierung:** Verhindert persistente "Geister-Verknüpfungen" durch Ablehnung irrelevanter Kanten.
|
||||
* **Deduplizierung:** Kanten werden anhand ihrer Identität erkannt. Die "stärkere" Provenance gewinnt.
|
||||
|
|
@ -1,223 +0,0 @@
|
|||
<!-- FILE: konzept_zielbild_kausales_retrieval_mindnet.md -->
|
||||
---
|
||||
id: konzept_zielbild_kausales_retrieval_mindnet
|
||||
title: Konzept & Zielbild – Kausalketten-Prüfung und kausales Retrieval für Mindnet (Qdrant)
|
||||
type: concept
|
||||
status: draft
|
||||
created: 2026-01-13
|
||||
lang: de
|
||||
tags:
|
||||
- mindnet
|
||||
- obsidian
|
||||
- knowledge_graph
|
||||
- causal_chains
|
||||
- retrieval
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Konzept & Zielbild – Kausalketten-Prüfung und kausales Retrieval für Mindnet (Qdrant)
|
||||
|
||||
## Ziel
|
||||
Mindnet soll zu beliebigen Fragestellungen **die richtigen Notizen** nicht nur über semantische Nähe (Embeddings), sondern über **kausale Relevanz** finden.
|
||||
Parallel soll ein Authoring-Assistent helfen, Obsidian-Notizen so anzulegen, dass Kausalketten **formal konsistent** und **traversierbar** sind.
|
||||
|
||||
---
|
||||
|
||||
## Ausgangslage / Problem
|
||||
- Der Wissensgraph wird in Qdrant gepflegt; aktuelles Retrieval basiert primär auf **Gewichtung + semantischer Nähe**.
|
||||
- Ergebnis: thematisch nahe Treffer, aber oft **nicht antwortrelevant** (fehlende Ursachen-/Folgenbezüge).
|
||||
- Obsidian-Notizen enthalten Edges (Vorwärts/Rückwärts); Qualität hängt von:
|
||||
- korrekter Relation (Kausalität vs Chronologie),
|
||||
- konsistenten Node-Namen,
|
||||
- Inversen (gegenläufigen Beziehungen),
|
||||
- sauberer Typisierung ab.
|
||||
|
||||
---
|
||||
|
||||
## Grundannahmen
|
||||
- Viele Antworten benötigen einen **Erklärungspfad** statt eines Einzel-Treffers:
|
||||
- Ursache → Mechanismus/Transformation → Entscheidung → Wirkung → Rückkopplung
|
||||
- Kausalität ist im Graph als gerichtete Kanten modelliert und über inverse Typen **bidirektional navigierbar**:
|
||||
- `resulted_in` ⇄ `caused_by`
|
||||
- `followed_by` ⇄ `preceeded_by`
|
||||
- `derived_from` ⇄ `source_of`
|
||||
- `impacts` ⇄ `impacted_by`
|
||||
|
||||
---
|
||||
|
||||
## System-Zielbild (2 Hauptkomponenten)
|
||||
|
||||
### 1) Authoring-Assistent (Obsidian Graph Linter + Chain Explorer)
|
||||
Zweck: Qualitätssicherung beim Erstellen/Ändern von Notizen.
|
||||
|
||||
**Kernfunktionen**
|
||||
- **Formale Prüfungen**
|
||||
- Canonical Edge vs Alias (Normalisierung nach `edge_vocabulary`)
|
||||
- Zielnoten existieren / leere Links als `open_question` oder TODO markieren
|
||||
- Tippfehler/Node-Splitting erkennen (mehrere Schreibweisen desselben Knoten)
|
||||
- Edge-Typ zulässig für Note-Typ (z.B. keine Kausal-Edges aus `open_question`)
|
||||
- **Semantische Plausibilität (regelbasiert)**
|
||||
- Chronologie (`followed_by`) ≠ Kausalität (`resulted_in`)
|
||||
- Hub-/Index-Noten nutzen primär `related_to/consists_of` statt Kausalität
|
||||
- Prinzipien bevorzugt `derived_from/based_on` statt pauschal `caused_by`
|
||||
- **Ketten-Integrität**
|
||||
- „Gap“-Warnungen (Sprünge ohne Zwischennoten)
|
||||
- Zyklen ohne Sinn (A caused_by B und B caused_by A)
|
||||
- Mehrfachursachen transparent markieren
|
||||
|
||||
**Outputs**
|
||||
- Lint-Report pro Note (Fehler/Warnung/Empfehlung)
|
||||
- Chain-Preview (2–4 Schritte vorwärts/rückwärts)
|
||||
- Optional: Auto-Fix-Vorschläge (Alias→Canonical, Link-Normalisierung, Inversen ergänzen)
|
||||
|
||||
---
|
||||
|
||||
### 2) Mindnet Retrieval: Hybrid aus Embeddings + Graph Traversal + Reranking
|
||||
Zweck: Aus einer Frage automatisch eine **kleine, kausal zusammenhängende** Menge von Notizen auswählen.
|
||||
|
||||
**Pipeline**
|
||||
1. **Seed Retrieval (Qdrant Embeddings)**
|
||||
- Top-K Kandidaten (z.B. 30) als Startpunkte
|
||||
- Optional: Filter nach Node-Typ (z.B. bei „Welche Entscheidungen…“)
|
||||
|
||||
2. **Intent-Klassifikation (Frage → Richtung & Kettenform)**
|
||||
- Regelbasiert (Start) oder später ML-Classifier
|
||||
- Output: `{direction, preferred_edges, target_types, max_hops, need_explanation_chain}`
|
||||
|
||||
3. **Graph Expansion (Multi-Source Multi-Hop Traversal)**
|
||||
- Expandiert von Seeds 1–3 Hops (typisch 2–4)
|
||||
- Richtungslogik:
|
||||
- „Warum/Ursache“ → rückwärts (`caused_by`, `preceeded_by`, `derived_from`)
|
||||
- „Folgen/Ergebnis“ → vorwärts (`resulted_in`, `followed_by`, `impacts`)
|
||||
- „Entwicklung/Veränderung“ → beides (forward + backward)
|
||||
- Ergebnis: Pfad-Kandidaten (nicht nur Nodes)
|
||||
|
||||
4. **Reranking (Antwortrelevanz)**
|
||||
- Score = Semantik + Pfadqualität + Antwortform-Passung
|
||||
|
||||
5. **Antwort-Bausteine (Minimal Explanation Subgraph)**
|
||||
- Merged Top-Pfade zu einem kleinen Subgraph (z.B. 8–12 Nodes)
|
||||
- Pruning nach zentralen Knoten und erklärender Kettenform
|
||||
|
||||
---
|
||||
|
||||
## Spezifikation: Intent → Traversal Mode (Heuristik)
|
||||
|
||||
### Intent-Struktur
|
||||
- `direction`: `backward | forward | both`
|
||||
- `preferred_edges`: Menge Edge-Typen
|
||||
- `target_types`: Menge Node-Typen
|
||||
- `max_hops`: int
|
||||
- `need_explanation_chain`: bool
|
||||
|
||||
### Heuristik (Deutsch)
|
||||
- **Warum / Ursache / Auslöser / wodurch / wie kam es dazu**
|
||||
- direction: backward
|
||||
- preferred_edges: `{caused_by, preceeded_by, derived_from}`
|
||||
- target_types: `{experience, decision, strategy, state}`
|
||||
- max_hops: 2–4
|
||||
- **Was führte zu / Folgen / Auswirkungen / resultierte in**
|
||||
- direction: forward
|
||||
- preferred_edges: `{resulted_in, followed_by, impacts}`
|
||||
- target_types: `{decision, strategy, state, principle}`
|
||||
- max_hops: 2–4
|
||||
- **Entwicklung / Veränderung / Weltbild / Glaubenssatz / Charakter**
|
||||
- direction: both
|
||||
- preferred_edges backward: `{caused_by, derived_from}`
|
||||
- preferred_edges forward: `{resulted_in, impacts}`
|
||||
- target_types: `{principle, state, strategy, decision}`
|
||||
- max_hops: 2–4
|
||||
- need_explanation_chain: true
|
||||
|
||||
---
|
||||
|
||||
## Spezifikation: Traversal (Weighted Multi-Hop)
|
||||
|
||||
### Gewichte (Startwerte)
|
||||
**Edge Weights**
|
||||
- `resulted_in`: 1.00
|
||||
- `caused_by`: 1.00
|
||||
- `derived_from`: 0.90
|
||||
- `source_of`: 0.90
|
||||
- `impacts`: 0.70
|
||||
- `impacted_by`: 0.70
|
||||
- `followed_by`: 0.50
|
||||
- `preceeded_by`: 0.50
|
||||
- `related_to`: 0.25
|
||||
- `part_of/consists_of`: 0.25
|
||||
|
||||
**Node-Type Weights**
|
||||
- `experience`: 1.00
|
||||
- `decision`: 1.00
|
||||
- `strategy`: 0.90
|
||||
- `state`: 0.85
|
||||
- `principle`: 0.85
|
||||
- `insight(hub)`: 0.35
|
||||
- `open_question/hypothesis/white_spot`: 0.00 (Filter)
|
||||
|
||||
**Hop Decay**
|
||||
- `hop_decay(h) = 0.75^h`
|
||||
|
||||
### Traversal-Logik (pseudocode-nah)
|
||||
- Multi-Source-Expansion ab Seeds
|
||||
- Pfade priorisiert nach kumuliertem Pfadscore
|
||||
- `visited` verhindert endlose Wiederholungen
|
||||
|
||||
---
|
||||
|
||||
## Spezifikation: Reranking (Semantik + Kausalität + Antwortform)
|
||||
|
||||
### Final Score
|
||||
- `final_score(path) = alpha*semantic + beta*coherence + gamma*shape_match`
|
||||
|
||||
Startwerte:
|
||||
- `alpha = 0.55` (Semantik)
|
||||
- `beta = 0.30` (Kausal-Kohärenz)
|
||||
- `gamma = 0.15` (Passung zur Frageform)
|
||||
|
||||
**Causal Coherence**
|
||||
- Bonus, wenn Pfad Kausal-Edges enthält (`resulted_in/caused_by/derived_from`)
|
||||
- Malus, wenn nur Navigation/Chronologie enthalten ist
|
||||
- Bonus für Kernform: `experience → decision → (state|strategy|principle)`
|
||||
|
||||
---
|
||||
|
||||
## Output: Minimal Explanation Subgraph (MES)
|
||||
Ziel: nicht eine Liste, sondern ein erklärendes Subgraph-Set.
|
||||
|
||||
**Regeln**
|
||||
- Top-Pfade (z.B. 3–5) mergen
|
||||
- max_nodes: 8–12
|
||||
- Pruning:
|
||||
- Hubs raus, wenn sie nur Navigation sind
|
||||
- Decision/Principle/State bevorzugen (Antwortanker)
|
||||
- Bridge-Nodes behalten (in mehreren Pfaden vorkommend)
|
||||
|
||||
---
|
||||
|
||||
## Authoring-Regeln (Graph-Hygiene) – harte Leitplanken
|
||||
1. Kausalität nur auf atomaren Noten (`experience/decision/state/strategy/principle`)
|
||||
2. Hubs/Indexnoten: primär `related_to/consists_of` (keine „Hub verursacht X“-Kausalität)
|
||||
3. Inverse Edges müssen erzeugbar sein (oder Build-Step erzeugt sie deterministisch)
|
||||
4. Chronologie strikt trennen (`followed_by` ≠ `resulted_in`)
|
||||
5. Prinzipien: `derived_from/based_on` für Herkunft (statt pauschal `caused_by`)
|
||||
6. Leere Links als `open_question` oder TODO ohne Kausal-Edge
|
||||
7. Kanonische Dateinamen: Node-Splitting verhindern
|
||||
|
||||
---
|
||||
|
||||
## Nutzen / Erfolgskriterien
|
||||
- **Bessere Answer Relevance**: Mindnet liefert Knoten mit erklärender Kausalstruktur statt nur thematischer Nähe
|
||||
- **Erklärbarkeit**: Antwort kann mit Pfad(en) begründet werden
|
||||
- **Debuggability**: Fehlantworten lassen sich auf falsche/fehlende Kanten zurückführen
|
||||
- **Authoring-Effizienz**: Assistent verhindert typische Edge-Fehler früh
|
||||
|
||||
---
|
||||
|
||||
## Offene Punkte (für nächste Iteration)
|
||||
- Intent-Taxonomie (8–12 Frageklassen) finalisieren und evaluieren
|
||||
- Welche Edges werden als „kausal“ im engeren Sinne akzeptiert?
|
||||
- Welche Node-Typen sind Pflichtmetadaten für Mindnet?
|
||||
- Evaluation: Retrieval-Qualität mit/ohne Traversal (A/B)
|
||||
|
||||
|
|
@ -1,454 +0,0 @@
|
|||
---
|
||||
doc_type: technical_reference
|
||||
audience: developer, integrator
|
||||
scope: api, endpoints, integration, agentic_rag
|
||||
status: active
|
||||
version: 2.9.3
|
||||
context: "Vollständige API-Referenz für alle Mindnet-Endpunkte inklusive WP-25 Agentic Multi-Stream RAG. Basis für Integration und Entwicklung."
|
||||
---
|
||||
|
||||
# API Reference
|
||||
|
||||
Diese Dokumentation beschreibt alle verfügbaren API-Endpunkte von Mindnet. Die API basiert auf FastAPI und nutzt automatische OpenAPI-Dokumentation (verfügbar unter `/docs`).
|
||||
|
||||
## Basis-URL
|
||||
|
||||
```
|
||||
http://localhost:8001 # Production
|
||||
http://localhost:8002 # Development
|
||||
```
|
||||
|
||||
## Health Check
|
||||
|
||||
### `GET /healthz`
|
||||
|
||||
Prüft den Status der API und der Verbindung zu Qdrant.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"qdrant": "http://localhost:6333",
|
||||
"prefix": "mindnet"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Query Endpoints
|
||||
|
||||
### `POST /query`
|
||||
|
||||
Führt eine hybride oder semantische Suche im Wissensgraph durch.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"query": "Was ist Mindnet?",
|
||||
"mode": "hybrid", // "hybrid" oder "semantic"
|
||||
"top_k": 10,
|
||||
"expand_depth": 1,
|
||||
"explain": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"query_id": "uuid-v4",
|
||||
"hits": [
|
||||
{
|
||||
"chunk_id": "uuid",
|
||||
"note_id": "uuid",
|
||||
"text": "Auszug...",
|
||||
"score": 0.85,
|
||||
"explanation": {
|
||||
"reasons": [...]
|
||||
}
|
||||
}
|
||||
],
|
||||
"stats": {
|
||||
"total_hits": 10,
|
||||
"query_time_ms": 45
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Hinweis:** Das Feedback-Logging erfolgt asynchron im Hintergrund (Background Tasks).
|
||||
|
||||
---
|
||||
|
||||
## Chat Endpoints
|
||||
|
||||
### `POST /chat/`
|
||||
|
||||
Hauptendpunkt für RAG-Chat und Interview-Modus. Unterstützt Streaming.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"message": "Was ist Mindnet?",
|
||||
"history": [], // Optional: Chat-Verlauf
|
||||
"stream": true // Optional: SSE-Streaming
|
||||
}
|
||||
```
|
||||
|
||||
**Response (Non-Streaming):**
|
||||
```json
|
||||
{
|
||||
"query_id": "uuid",
|
||||
"answer": "Mindnet ist ein persönliches KI-Gedächtnis...",
|
||||
"sources": [
|
||||
{
|
||||
"node_id": "uuid",
|
||||
"note_id": "uuid",
|
||||
"semantic_score": 0.85,
|
||||
"total_score": 0.92,
|
||||
"stream_origin": "values_stream", // WP-25: Ursprungs-Stream
|
||||
"explanation": {...}
|
||||
}
|
||||
],
|
||||
"latency_ms": 450,
|
||||
"intent": "DECISION", // WP-25: Gewählte Strategie
|
||||
"intent_source": "Keyword (FastPath)" // WP-25: Quelle der Intent-Erkennung
|
||||
}
|
||||
```
|
||||
|
||||
**Response (Streaming):**
|
||||
Server-Sent Events (SSE) mit Chunks der Antwort.
|
||||
|
||||
**Intent-Typen (WP-25):**
|
||||
- `FACT_WHAT`: Wissensabfrage (Wissen/Listen)
|
||||
- `FACT_WHEN`: Zeitpunkte (Termine, Daten)
|
||||
- `DECISION`: Entscheidungsfrage (Beratung, Strategie)
|
||||
- `EMPATHY`: Emotionale Anfrage (Reflexion)
|
||||
- `CODING`: Technische Anfrage (Programmierung)
|
||||
- `INTERVIEW`: Wissen erfassen (Datenerfassung)
|
||||
|
||||
**Stream-Tracing (WP-25):**
|
||||
Jeder Treffer in `sources` enthält `stream_origin`, um die Zuordnung zum Quell-Stream zu ermöglichen (z.B. "values_stream", "facts_stream", "risk_stream").
|
||||
|
||||
---
|
||||
|
||||
## Graph Endpoints
|
||||
|
||||
### `GET /graph/{note_id}`
|
||||
|
||||
Lädt den Subgraphen um eine Note herum.
|
||||
|
||||
**Query Parameters:**
|
||||
- `depth` (int, default: 1): Tiefe der Graph-Expansion
|
||||
- `edge_types` (List[str], optional): Filter für Kanten-Typen
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"center_note_id": "uuid",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"type": "project",
|
||||
"title": "Projekt Alpha",
|
||||
"tags": ["ki"],
|
||||
"in_degree": 5,
|
||||
"out_degree": 3
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "uuid->uuid:depends_on",
|
||||
"kind": "depends_on",
|
||||
"source": "uuid",
|
||||
"target": "uuid",
|
||||
"target_section": "P3 – Disziplin", // Optional: Abschnitts-Name bei Deep-Links
|
||||
"weight": 1.4,
|
||||
"direction": "out",
|
||||
"provenance": "explicit",
|
||||
"confidence": 1.0
|
||||
}
|
||||
],
|
||||
"stats": {
|
||||
"node_count": 10,
|
||||
"edge_count": 15
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ingest Endpoints
|
||||
|
||||
### `POST /ingest/analyze`
|
||||
|
||||
Analysiert einen Text-Draft und liefert Link-Vorschläge (Intelligence-Feature).
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"text": "Markdown-Text...",
|
||||
"type": "concept" // Optional: Notiz-Typ
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"suggestions": [
|
||||
{
|
||||
"text": "Mindnet",
|
||||
"type": "exact_match",
|
||||
"note_id": "uuid",
|
||||
"confidence": 0.95
|
||||
},
|
||||
{
|
||||
"text": "KI-Gedächtnis",
|
||||
"type": "semantic",
|
||||
"note_id": "uuid",
|
||||
"confidence": 0.82
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /ingest/save`
|
||||
|
||||
Speichert eine Notiz und startet die Ingestion im Hintergrund (WP-14: Background Tasks).
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"markdown_content": "---\nid: ...\n---\n# Titel\n...",
|
||||
"filename": "meine-notiz.md", // Optional
|
||||
"folder": "00_Inbox" // Optional, default: "00_Inbox"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "queued",
|
||||
"file_path": "00_Inbox/meine-notiz.md",
|
||||
"note_id": "pending",
|
||||
"message": "Speicherung & Hybrid-KI-Analyse (WP-20) im Hintergrund gestartet.",
|
||||
"stats": {
|
||||
"chunks": -1, // -1 = Async Processing
|
||||
"edges": -1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Wichtig:**
|
||||
- Die Datei wird sofort auf der Festplatte gespeichert
|
||||
- Die Ingestion (Chunking, Embedding, Smart Edges) läuft asynchron im Hintergrund
|
||||
- Dies verhindert Timeouts bei großen Dateien oder langsamen LLM-Calls
|
||||
|
||||
---
|
||||
|
||||
## Feedback Endpoints
|
||||
|
||||
### `POST /feedback`
|
||||
|
||||
Nimmt explizites User-Feedback entgegen (WP-04c).
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"query_id": "uuid",
|
||||
"rating": 5, // 1-5 Sterne
|
||||
"feedback_type": "global", // "global" oder "granular"
|
||||
"chunk_id": "uuid" // Optional für granular feedback
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "recorded",
|
||||
"query_id": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tools Endpoints
|
||||
|
||||
### `GET /tools/ollama`
|
||||
|
||||
Liefert JSON-Schemas für die Integration als Tools in externe Agenten (Ollama, OpenAI, etc.).
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"tools": [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "mindnet_query",
|
||||
"description": "Hybrid-Retrieval über mindnet (Semantik + Edges).",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Freitext-Query"
|
||||
},
|
||||
"top_k": {
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"minimum": 1,
|
||||
"maximum": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "mindnet_subgraph",
|
||||
"description": "Gibt die Nachbarschaft (Edges) einer Note/Seed-ID zurück.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"note_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"depth": {
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"minimum": 0,
|
||||
"maximum": 3
|
||||
}
|
||||
},
|
||||
"required": ["note_id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Verwendung:** Diese Schemas können direkt in Ollama-Function-Calling oder OpenAI-Tools integriert werden.
|
||||
|
||||
---
|
||||
|
||||
## Admin Endpoints
|
||||
|
||||
### `GET /admin/stats`
|
||||
|
||||
Liefert Statistiken über die Qdrant-Collections und die aktuelle Konfiguration.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"collections": {
|
||||
"notes": {
|
||||
"name": "mindnet_notes",
|
||||
"count": 150
|
||||
},
|
||||
"chunks": {
|
||||
"name": "mindnet_chunks",
|
||||
"count": 1250
|
||||
},
|
||||
"edges": {
|
||||
"name": "mindnet_edges",
|
||||
"count": 3200
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"qdrant": "http://localhost:6333",
|
||||
"prefix": "mindnet",
|
||||
"vector_size": 768,
|
||||
"distance": "Cosine",
|
||||
"retriever": {
|
||||
"w_sem": 0.70,
|
||||
"w_edge": 0.25,
|
||||
"w_cent": 0.05,
|
||||
"top_k": 10,
|
||||
"expand_depth": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Hinweis:** Dieser Endpunkt ist optional und kann deaktiviert sein, wenn der `admin`-Router nicht geladen wird.
|
||||
|
||||
---
|
||||
|
||||
## Fehlerbehandlung
|
||||
|
||||
Alle Endpunkte verwenden standardisierte HTTP-Status-Codes:
|
||||
|
||||
- `200 OK`: Erfolgreiche Anfrage
|
||||
- `201 Created`: Ressource erfolgreich erstellt
|
||||
- `400 Bad Request`: Ungültige Anfrage (z.B. fehlende Parameter)
|
||||
- `500 Internal Server Error`: Server-Fehler
|
||||
|
||||
**Fehler-Response-Format:**
|
||||
```json
|
||||
{
|
||||
"detail": "Fehlerbeschreibung"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting & Timeouts
|
||||
|
||||
- **Chat-Endpunkte:** Keine Retries (max_retries=0) für schnelle Fallback-Kaskade
|
||||
- **Ingest-Endpunkte:** Background Tasks verhindern Timeouts
|
||||
- **Query-Endpunkte:** Standard-Timeout konfigurierbar via `MINDNET_API_TIMEOUT`
|
||||
|
||||
---
|
||||
|
||||
## OpenAPI Dokumentation
|
||||
|
||||
Die vollständige interaktive API-Dokumentation ist verfügbar unter:
|
||||
- Swagger UI: `http://localhost:8001/docs`
|
||||
- ReDoc: `http://localhost:8001/redoc`
|
||||
- OpenAPI JSON: `http://localhost:8001/openapi.json`
|
||||
|
||||
---
|
||||
|
||||
## Integration Beispiele
|
||||
|
||||
### Python (requests)
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# Query
|
||||
response = requests.post(
|
||||
"http://localhost:8001/query",
|
||||
json={"query": "Was ist Mindnet?", "top_k": 5}
|
||||
)
|
||||
print(response.json())
|
||||
|
||||
# Chat
|
||||
response = requests.post(
|
||||
"http://localhost:8001/chat/",
|
||||
json={"message": "Erkläre mir Mindnet"}
|
||||
)
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
### cURL
|
||||
|
||||
```bash
|
||||
# Health Check
|
||||
curl http://localhost:8001/healthz
|
||||
|
||||
# Query
|
||||
curl -X POST http://localhost:8001/query \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "Was ist Mindnet?", "top_k": 5}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Weitere Informationen
|
||||
|
||||
- **Chat-Backend Details:** Siehe [Chat Backend Dokumentation](03_tech_chat_backend.md)
|
||||
- **Ingestion-Pipeline:** Siehe [Ingestion Pipeline](03_tech_ingestion_pipeline.md)
|
||||
- **Retrieval & Scoring:** Siehe [Retrieval & Scoring](03_tech_retrieval_scoring.md)
|
||||
|
||||
|
|
@ -1,174 +1,57 @@
|
|||
---
|
||||
doc_type: technical_reference
|
||||
audience: developer, architect
|
||||
scope: backend, chat, llm_service, traffic_control, resilience, agentic_rag, moe, lazy_prompts
|
||||
scope: backend, chat, llm_service, traffic_control, resilience
|
||||
status: active
|
||||
version: 3.1.1
|
||||
context: "Technische Implementierung des FastAPI-Routers, des hybriden LLMService (v3.5.5), WP-25 Agentic Multi-Stream RAG, WP-25a Mixture of Experts (MoE), WP-25b Lazy-Prompt-Orchestration und WP-20 Resilienz-Logik."
|
||||
version: 2.8.1
|
||||
context: "Technische Implementierung des FastAPI-Routers, des hybriden LLMService (v3.3.6) und der WP-20 Resilienz-Logik."
|
||||
---
|
||||
|
||||
# Chat Backend & Agentic Multi-Stream RAG
|
||||
# Chat Backend & Traffic Control
|
||||
|
||||
## 1. Hybrid Router & Intent-basiertes Routing (WP-25)
|
||||
## 1. Hybrid Router (Decision Engine)
|
||||
|
||||
Der zentrale Einstiegspunkt für jede Chatanfrage ist der **Hybrid Router** (`app/routers/chat.py`). Seit WP-25 agiert das System als **Agentic Orchestrator**, der Nutzeranfragen analysiert, in parallele Wissens-Streams aufteilt und diese zu einer kontextreichen, wertebasierten Antwort synthetisiert.
|
||||
Der zentrale Einstiegspunkt für jede Chatanfrage ist der **Hybrid Router** (`app/routers/chat.py`). Er entscheidet dynamisch über die Strategie und nutzt den `LLMService` zur provider-agnostischen Generierung.
|
||||
|
||||
### 1.1 Intent-Erkennung (Hybrid-Modus - WP-25b)
|
||||
### 1.1 Intent-Erkennung (Logik)
|
||||
|
||||
Der Router nutzt einen **Hybrid-Modus** mit Keyword-Fast-Path und LLM-Slow-Path:
|
||||
Der Router prüft den Input in drei Stufen (Wasserfall-Prinzip):
|
||||
|
||||
1. **Keyword Fast-Path (Sofortige Erkennung):**
|
||||
* Prüft `trigger_keywords` aus `decision_engine.yaml` (z.B. "Soll ich", "Wann", "Was ist").
|
||||
* Wenn Match: Sofortige Intent-Zuordnung ohne LLM-Call.
|
||||
* **Strategien:** FACT_WHAT, FACT_WHEN, DECISION, EMPATHY, CODING, INTERVIEW.
|
||||
2. **Type Keywords (Interview-Modus):**
|
||||
* Lädt `types.yaml` und prüft `detection_keywords` für Objekt-Erkennung.
|
||||
* Wenn Match und keine Frage: **INTERVIEW Modus** (Datenerfassung).
|
||||
3. **LLM Slow-Path (Semantische Analyse - WP-25b):**
|
||||
* Wenn unklar: Anfrage an `DecisionEngine._determine_strategy()` zur LLM-basierten Klassifizierung.
|
||||
* **Lazy-Prompt-Loading:** Nutzt `prompt_key="intent_router_v1"` mit `variables={"query": query}`
|
||||
* **Ultra-robustes Parsing:** Regex-basierter Intent-Parser bereinigt Modell-Artefakte (z.B. `CODING[/S]` → `CODING`)
|
||||
* **Fallback:** Bei unklarem Intent → `FACT_WHAT`
|
||||
1. **Question Detection (Regelbasiert):**
|
||||
* Prüfung auf Vorhandensein von `?` oder W-Wörtern (Wer, Wie, Was, Soll ich).
|
||||
* Wenn positiv: **RAG Modus** (Interview wird blockiert).
|
||||
2. **Keyword Scan (Fast Path):**
|
||||
* Lädt `types.yaml` (Objekte) und `decision_engine.yaml` (Handlungen).
|
||||
* Wenn Match (z.B. "Projekt" + "neu"): **INTERVIEW Modus**.
|
||||
3. **LLM Fallback (Slow Path):**
|
||||
* Wenn unklar: Anfrage an LLM zur Klassifizierung mittels `router_prompt`.
|
||||
|
||||
### 1.2 Mixture of Experts (MoE) Architektur (WP-25a)
|
||||
### 1.2 Prompt-Auflösung (Bulletproof Resolution)
|
||||
|
||||
Seit WP-25a nutzt MindNet eine **profilbasierte Experten-Steuerung** statt einer globalen Provider-Konfiguration. Jede Systemaufgabe wird einem dedizierten Profil zugewiesen:
|
||||
Um Kompatibilitätsprobleme mit verschachtelten YAML-Prompts zu vermeiden, nutzt der Router die Methode `llm.get_prompt()`. Diese implementiert eine **Provider-Kaskade**:
|
||||
* **Spezifischer Provider:** Das System sucht zuerst nach einem Prompt für den aktiv konfigurierten Provider (z.B. `openrouter`).
|
||||
* **Cloud-Stil Fallback:** Existiert dieser nicht, erfolgt ein Fallback auf das `gemini`-Template.
|
||||
* **Basis-Fallback:** Als letzte Instanz wird das `ollama`-Template geladen.
|
||||
* **String-Garantie:** Die Methode garantiert die Rückgabe eines Strings (selbst bei verschachtelten YAML-Dicts), was 500-Fehler bei String-Operationen wie `.replace()` oder `.format()` verhindert.
|
||||
|
||||
**Profil-Registry (`llm_profiles.yaml`):**
|
||||
* **`synthesis_pro`:** Hochwertige Synthese für Chat-Antworten
|
||||
* **`tech_expert`:** Fachspezialist für Code & Technik
|
||||
* **`compression_fast`:** Schnelle Kompression & Routing
|
||||
* **`ingest_validator`:** Deterministische Validierung (Temperature 0.0)
|
||||
* **`identity_safe`:** Lokaler Anker (Ollama/Phi-3) für maximale Privacy
|
||||
### 1.3 RAG Flow (Technisch)
|
||||
|
||||
**Rekursive Fallback-Kaskade:**
|
||||
Der `LLMService` (v3.5.2) implementiert eine automatische Fallback-Logik:
|
||||
1. Versucht primäres Profil (z.B. `synthesis_pro`)
|
||||
2. Bei Fehler → `fallback_profile` (z.B. `synthesis_backup`)
|
||||
3. Bei weiterem Fehler → nächster Fallback (z.B. `identity_safe`)
|
||||
4. Schutz gegen Zirkel-Referenzen via `visited_profiles`-Tracking
|
||||
Wenn der Intent `FACT` oder `DECISION` ist, wird folgender Flow ausgeführt:
|
||||
|
||||
**Integration:**
|
||||
* **Intent-Routing:** Nutzt `router_profile` (z.B. `compression_fast`)
|
||||
* **Stream-Kompression:** Nutzt `compression_profile` pro Stream
|
||||
* **Synthese:** Nutzt `llm_profile` aus Strategie-Konfiguration
|
||||
* **Ingestion:** Nutzt `ingest_validator` für binäre Validierungen
|
||||
|
||||
### 1.3 Hierarchisches Prompt-Resolution-System (WP-25b)
|
||||
|
||||
Seit WP-25b nutzt MindNet eine **dreistufige hierarchische Prompt-Auflösung** mit Lazy-Loading. Prompts werden erst im Moment des Modellaustauschs geladen, basierend auf dem exakt aktiven Modell.
|
||||
|
||||
**Hierarchische Auflösung (`llm_service.py` v3.5.5):**
|
||||
|
||||
1. **Level 1 (Modell-ID):** Suche nach exakten Übereinstimmungen für die Modell-ID (z.B. `google/gemini-2.0-flash-exp:free`).
|
||||
* **Vorteil:** Modell-spezifische Optimierungen (z.B. für Gemini 2.0, Llama 3.3, Qwen 2.5)
|
||||
* **Logging:** `🎯 [PROMPT-TRACE] Level 1 Match: Model-specific`
|
||||
|
||||
2. **Level 2 (Provider):** Fallback auf allgemeine Provider-Anweisungen (z.B. `openrouter` oder `ollama`).
|
||||
* **Vorteil:** Bewährte Standards aus v3.1.2 bleiben erhalten
|
||||
* **Logging:** `📡 [PROMPT-TRACE] Level 2 Match: Provider-fallback`
|
||||
|
||||
3. **Level 3 (Default):** Globaler Sicherheits-Satz zur Vermeidung von Fehlern bei unbekannten Konfigurationen.
|
||||
* **Fallback-Kette:** `default` → `gemini` → `ollama` → `""`
|
||||
* **Logging:** `⚓ [PROMPT-TRACE] Level 3 Match: Global Default`
|
||||
|
||||
**Lazy-Prompt-Orchestration:**
|
||||
* **Lazy Loading:** Prompts werden erst zur Laufzeit geladen, wenn das aktive Modell bekannt ist
|
||||
* **Parameter:** `prompt_key` und `variables` statt vorformatierter Strings
|
||||
* **Vorteil:** Maximale Resilienz bei Modell-Fallbacks (Cloud → Local)
|
||||
* **Traceability:** Vollständige Transparenz über genutzte Instruktionen via `[PROMPT-TRACE]` Logs
|
||||
|
||||
**String-Garantie:**
|
||||
Die Methode garantiert die Rückgabe eines Strings (selbst bei verschachtelten YAML-Dicts), was 500-Fehler bei String-Operationen wie `.replace()` oder `.format()` verhindert.
|
||||
|
||||
### 1.4 Multi-Stream Retrieval (WP-25)
|
||||
|
||||
Anstelle einer einzelnen Suche führt die `DecisionEngine` nun **parallele Abfragen** in spezialisierten Streams aus:
|
||||
|
||||
**Stream-Library (definiert in `decision_engine.yaml`):**
|
||||
* **Values Stream:** Extrahiert Identität, Ethik und Prinzipien (`value`, `principle`, `belief`, etc.).
|
||||
* **Facts Stream:** Liefert operative Daten zu Projekten, Tasks und Status (`project`, `decision`, `task`, etc.).
|
||||
* **Biography Stream:** Greift auf persönliche Erfahrungen und Journal-Einträge zu (`experience`, `journal`, `profile`).
|
||||
* **Risk Stream:** Identifiziert Hindernisse und potenzielle Gefahren (`risk`, `obstacle`, `bias`).
|
||||
* **Tech Stream:** Bündelt technisches Wissen, Code und Dokumentation (`concept`, `source`, `glossary`, etc.).
|
||||
|
||||
**Stream-Konfiguration:**
|
||||
* Jeder Stream nutzt individuelle **Edge-Boosts** (z.B. `guides: 3.0` für Values Stream).
|
||||
* **Filter-Types** sind strikt mit `types.yaml` (v2.7.0) synchronisiert.
|
||||
* **Query-Templates** transformieren die ursprüngliche Anfrage für spezialisierte Suche.
|
||||
|
||||
**Parallele Ausführung:**
|
||||
* `asyncio.gather()` führt alle aktiven Streams gleichzeitig aus.
|
||||
* **Stream-Tracing:** Jeder Treffer wird mit `stream_origin` markiert für Feedback-Optimierung.
|
||||
* **Fehlerbehandlung:** Einzelne Stream-Fehler blockieren nicht die gesamte Anfrage.
|
||||
|
||||
### 1.5 Pre-Synthesis Kompression (WP-25a)
|
||||
|
||||
Wissens-Streams, die den Schwellenwert (`compression_threshold`) überschreiten, werden **asynchron verdichtet**, bevor sie die Synthese erreichen:
|
||||
|
||||
**Kompression-Logik:**
|
||||
* **Schwellenwert:** Konfigurierbar pro Stream (z.B. 2500 Zeichen für Values Stream)
|
||||
* **Profil:** Nutzt `compression_profile` (z.B. `compression_fast` für schnelle Zusammenfassung)
|
||||
* **Parallelisierung:** Mehrere Streams können gleichzeitig komprimiert werden
|
||||
* **Fehlerbehandlung:** Kompressions-Fehler blockieren nicht die Synthese (Original-Content wird verwendet)
|
||||
|
||||
**Vorteile:**
|
||||
* Reduziert Token-Verbrauch bei langen Streams
|
||||
* Beschleunigt Synthese durch kürzere Kontexte
|
||||
* Erhält Relevanz durch intelligente Zusammenfassung
|
||||
|
||||
### 1.6 Wissens-Synthese (WP-25/25a)
|
||||
|
||||
Die Zusammenführung der Daten erfolgt über spezialisierte Templates in der `prompts.yaml`:
|
||||
|
||||
**Template-Struktur:**
|
||||
* Explizite Variablen für jeden Stream (z.B. `{values_stream}`, `{risk_stream}`).
|
||||
* **Pre-Initialization:** Alle möglichen Stream-Variablen werden vorab initialisiert (verhindert KeyErrors).
|
||||
* **Provider-spezifische Templates:** Separate Versionen für Ollama, Gemini und OpenRouter.
|
||||
|
||||
**Synthese-Strategien (Profil-gesteuert):**
|
||||
* **FACT_WHAT/FACT_WHEN:** Nutzt `synthesis_pro` - Kombiniert Fakten, Biographie und Technik.
|
||||
* **DECISION:** Nutzt `synthesis_pro` - Wägt Fakten gegen Werte ab, evaluiert Risiken.
|
||||
* **EMPATHY:** Nutzt `synthesis_pro` - Fokus auf Biographie und Werte.
|
||||
* **CODING:** Nutzt `tech_expert` - Spezialisiertes Modell für Code & Technik.
|
||||
|
||||
**Profil-Auflösung:**
|
||||
Jede Strategie kann ein individuelles `llm_profile` definieren. Fehlt diese Angabe, wird `synthesis_pro` als Standard verwendet.
|
||||
|
||||
### 1.7 RAG Flow (Technisch - WP-25a)
|
||||
|
||||
Wenn der Intent nicht `INTERVIEW` ist, wird folgender Flow ausgeführt:
|
||||
|
||||
1. **Intent Detection:** Hybrid Router klassifiziert die Anfrage via `router_profile` (z.B. `compression_fast`).
|
||||
2. **Multi-Stream Retrieval:**
|
||||
* Parallele Abfragen in spezialisierten Streams via `DecisionEngine._execute_parallel_streams()`.
|
||||
* Jeder Stream nutzt individuelle Filter, Edge-Boosts und Query-Templates.
|
||||
3. **Pre-Synthesis Kompression (WP-25a):**
|
||||
* Streams über `compression_threshold` werden via `compression_profile` verdichtet.
|
||||
* Parallelisierung über `asyncio.gather()` für mehrere Streams gleichzeitig.
|
||||
4. **Wissens-Synthese:**
|
||||
* Strategie-spezifisches `llm_profile` (z.B. `tech_expert` für CODING) steuert die finale Antwortgenerierung.
|
||||
* Fallback-Kaskade bei Fehlern (automatisch via LLMService).
|
||||
3. **Context Formatting:**
|
||||
* Stream-Ergebnisse werden in formatierte Kontext-Strings umgewandelt.
|
||||
* **Ollama Context-Throttling:** Kontext wird auf `MAX_OLLAMA_CHARS` begrenzt (Standard: 10.000).
|
||||
4. **Synthese:**
|
||||
* `DecisionEngine._generate_final_answer()` kombiniert alle Streams.
|
||||
* Template-basierte Prompt-Konstruktion mit Stream-Variablen.
|
||||
5. **Response:**
|
||||
* LLM-Antwort wird generiert (provider-spezifisch).
|
||||
* **Sources:** Alle Treffer aus allen Streams werden dedupliziert und zurückgegeben.
|
||||
1. **Pre-Processing:** Query Rewriting (optional).
|
||||
2. **Context Enrichment:**
|
||||
* Abruf via `retriever.py` (Hybrid Search).
|
||||
* Integration von **Edge Boosts** aus der `decision_engine.yaml` zur Beeinflussung der Graph-Gewichtung.
|
||||
* Injection von Metadaten (`[TYPE]`, `[SCORE]`) in den Prompt.
|
||||
3. **Prompt Construction:** Assembly aus System-Prompt (Persona) + Context + Query.
|
||||
4. **Streaming:** LLM-Antwort wird via **SSE (Server-Sent Events)** an den Client gestreamt.
|
||||
5. **Post-Processing:** Anhängen des `Explanation` Layers (JSON-Breakdown) an das Ende des Streams.
|
||||
|
||||
---
|
||||
|
||||
## 2. LLM Service & Traffic Control (WP-20 / WP-25)
|
||||
## 2. LLM Service & Traffic Control (WP-20)
|
||||
|
||||
Der `LLMService` (`app/services/llm_service.py`, v3.4.2) fungiert als zentraler Hybrid-Client für OpenRouter, Google Gemini und Ollama. Er schützt das System vor Überlastung und verwaltet Quoten.
|
||||
|
||||
**WP-25 Integration:**
|
||||
* **Lazy Initialization:** `DecisionEngine` wird erst bei Bedarf initialisiert (verhindert Circular Imports).
|
||||
* **Ingest-Stability Patch:** Entfernung des <5-Zeichen Guards ermöglicht YES/NO Validierungen beim Vault-Import.
|
||||
* **Empty Response Guard:** Sicherung gegen leere `choices` Arrays bei OpenRouter (verhindert JSON-Errors).
|
||||
Der `LLMService` (`app/services/llm_service.py`) fungiert als zentraler Hybrid-Client für OpenRouter, Google Gemini und Ollama. Er schützt das System vor Überlastung und verwaltet Quoten.
|
||||
|
||||
Mit Version 2.8.1 wurde die Architektur der Antwort-Generierung grundlegend gehärtet:
|
||||
|
||||
|
|
@ -224,27 +107,10 @@ In v2.8 wurde ein intelligentes Fehler-Handling für Cloud-Provider implementier
|
|||
|
||||
---
|
||||
|
||||
## 4. Feedback Traceability & Stream-Tracing (WP-25)
|
||||
## 4. Feedback Traceability
|
||||
|
||||
Unterstützt das geplante Self-Tuning (WP08) und ermöglicht Stream-spezifische Optimierung.
|
||||
Unterstützt das geplante Self-Tuning (WP08).
|
||||
|
||||
1. **Query ID:** Generiert bei jedem `/chat` Call eine `UUIDv4`.
|
||||
2. **Stream-Tracing:** Jeder Treffer enthält `stream_origin` für Zuordnung zum Quell-Stream.
|
||||
3. **Logging:** Speichert einen Snapshot in `data/logs/query_snapshot.jsonl` (Input + Retrieved Context + Intent).
|
||||
4. **Feedback:** Der `/feedback` Endpoint verknüpft das User-Rating (1-5) mit der `query_id` und `stream_origin`.
|
||||
|
||||
## 5. Lifespan Management (WP-25)
|
||||
|
||||
Die FastAPI-Anwendung (`app/main.py`, v1.0.0) implementiert **Lifespan-Management** für sauberen Startup und Shutdown:
|
||||
|
||||
**Startup:**
|
||||
* Integritäts-Check der WP-25 Konfiguration (`decision_engine.yaml`, `prompts.yaml`).
|
||||
* Validierung kritischer Dateien vor dem Start.
|
||||
|
||||
**Shutdown:**
|
||||
* Ressourcen-Cleanup (LLMService-Connections schließen).
|
||||
* Graceful Shutdown für asynchrone Prozesse.
|
||||
|
||||
**Globale Fehlerbehandlung:**
|
||||
* Fängt unerwartete Fehler in der Multi-Stream Kette ab.
|
||||
* Strukturierte JSON-Responses bei Engine-Fehlern.
|
||||
1. **Query ID:** Generiert bei jedem `/query` Call eine `UUIDv4`.
|
||||
2. **Logging:** Speichert einen Snapshot in `data/logs/query_snapshot.jsonl` (Input + Retrieved Context).
|
||||
3. **Feedback:** Der `/feedback` Endpoint verknüpft das User-Rating (1-5) mit der `query_id`.
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
doc_type: technical_reference
|
||||
audience: developer, admin
|
||||
scope: configuration, env, registry, scoring, resilience, modularization, agentic_rag, moe, lazy_prompts, agentic_validation
|
||||
scope: configuration, env, registry, scoring, resilience, modularization
|
||||
status: active
|
||||
version: 4.5.8
|
||||
context: "Umfassende Referenztabellen für Umgebungsvariablen (inkl. Hybrid-Cloud & WP-76), YAML-Konfigurationen, Edge Registry Struktur, WP-25 Multi-Stream RAG, WP-25a Mixture of Experts (MoE), WP-25b Lazy-Prompt-Orchestration und WP-24c Phase 3 Agentic Edge Validation (v4.5.8) unter Berücksichtigung von WP-14."
|
||||
version: 2.9.1
|
||||
context: "Umfassende Referenztabellen für Umgebungsvariablen (inkl. Hybrid-Cloud & WP-76), YAML-Konfigurationen und die Edge Registry Struktur unter Berücksichtigung von WP-14."
|
||||
---
|
||||
|
||||
# Konfigurations-Referenz
|
||||
|
|
@ -20,15 +20,13 @@ Diese Variablen steuern die Infrastruktur, Pfade und globale Timeouts. Seit der
|
|||
| `QDRANT_URL` | `http://localhost:6333` | URL zur Vektor-DB API. |
|
||||
| `QDRANT_API_KEY` | *(leer)* | Optionaler Key für Absicherung. |
|
||||
| `COLLECTION_PREFIX` | `mindnet` | Namensraum für Collections (erzeugt `{prefix}_notes` etc). |
|
||||
| `MINDNET_PREFIX` | *(leer)* | **Alternative zu COLLECTION_PREFIX.** Falls gesetzt, wird dieser Wert verwendet. |
|
||||
| `VECTOR_DIM` | `768` | **Muss 768 sein** (für Nomic Embeddings). |
|
||||
| `MINDNET_DISTANCE` | `Cosine` | Metrik für Vektor-Ähnlichkeit (`Cosine`, `Euclidean`, `Dot`). |
|
||||
| `MINDNET_VECTOR_NAME` | `default` | **Neu (WP-14):** Basis-Vektorname für Named Vectors Support. |
|
||||
| `NOTES_VECTOR_NAME` | *(leer)* | **Neu (WP-14):** Spezifischer Vektorname für die Notes-Collection (Override). |
|
||||
| `CHUNKS_VECTOR_NAME` | *(leer)* | **Neu (WP-14):** Spezifischer Vektorname für die Chunks-Collection (Override). |
|
||||
| `EDGES_VECTOR_NAME` | *(leer)* | **Neu (WP-14):** Spezifischer Vektorname für die Edges-Collection (Override). |
|
||||
| `MINDNET_VOCAB_PATH` | *(Pfad)* | **Neu (WP-22):** Absoluter Pfad zur `01_edge_vocabulary.md`. Definiert den Ort des Dictionarys. |
|
||||
| `MINDNET_VAULT_ROOT` | `./vault_master` | **Achtung:** Standard ist `./vault_master`, nicht `./vault`! Basis-Pfad für Datei-Operationen. |
|
||||
| `MINDNET_VAULT_ROOT` | `./vault` | Basis-Pfad für Datei-Operationen. |
|
||||
| `MINDNET_TYPES_FILE` | `config/types.yaml` | Pfad zur Typ-Registry. |
|
||||
| `MINDNET_RETRIEVER_CONFIG`| `config/retriever.yaml`| Pfad zur Scoring-Konfiguration. |
|
||||
| `MINDNET_DECISION_CONFIG` | `config/decision_engine.yaml` | Pfad zur Router & Intent Config. |
|
||||
|
|
@ -50,11 +48,6 @@ Diese Variablen steuern die Infrastruktur, Pfade und globale Timeouts. Seit der
|
|||
| `MINDNET_LL_BACKGROUND_LIMIT`| `2` | **Traffic Control:** Max. parallele Hintergrund-Tasks (Semaphore). |
|
||||
| `MINDNET_CHANGE_DETECTION_MODE` | `full` | `full` (Text + Meta) oder `body` (nur Text). |
|
||||
| `MINDNET_DEFAULT_RETRIEVER_WEIGHT` | `1.0` | **Neu (WP-22):** Systemweiter Standard für das Retriever-Gewicht einer Notiz. |
|
||||
| `MINDNET_LLM_VALIDATION_HEADERS` | `Unzugeordnete Kanten,Edge Pool,Candidates` | **Neu (v4.2.0, WP-24c):** Komma-separierte Header-Namen für LLM-Validierung. Kanten in diesen Zonen erhalten `candidate:` Präfix und werden in Phase 3 validiert. |
|
||||
| `MINDNET_LLM_VALIDATION_HEADER_LEVEL` | `3` | **Neu (v4.2.0, WP-24c):** Header-Ebene für LLM-Validierung (1-6, Default: 3 für ###). Bestimmt, welche Überschriften als Validierungs-Zonen erkannt werden. |
|
||||
| `MINDNET_NOTE_SCOPE_ZONE_HEADERS` | `Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen` | **Neu (v4.2.0, WP-24c):** Komma-separierte Header-Namen für Note-Scope Zonen. Links in diesen Zonen werden als `scope: note` behandelt und nutzen Note-Summary/Text in Phase 3 Validierung. |
|
||||
| `MINDNET_NOTE_SCOPE_HEADER_LEVEL` | `2` | **Neu (v4.2.0, WP-24c):** Header-Ebene für Note-Scope Zonen (1-6, Default: 2 für ##). Bestimmt, welche Überschriften als Note-Scope Zonen erkannt werden. |
|
||||
| `MINDNET_IGNORE_FOLDERS` | *(leer)* | **Neu (v4.1.0):** Komma-separierte Liste von Ordnernamen, die beim Import ignoriert werden. Beispiel: `.trash,.obsidian,.git,.sync` |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -229,16 +222,15 @@ Die Datei muss eine Markdown-Tabelle enthalten, die vom Regex-Parser gelesen wir
|
|||
|
||||
---
|
||||
|
||||
## 5. Decision Engine (`decision_engine.yaml` v3.1.6)
|
||||
## 5. Decision Engine (`decision_engine.yaml`)
|
||||
|
||||
Die Decision Engine fungiert als zentraler **Agentic Orchestrator** für die Intent-Erkennung und das dynamische Multi-Stream Retrieval-Routing (WP-25). Sie bestimmt, wie das System auf eine Nutzeranfrage reagiert, welche Wissens-Streams aktiviert werden und wie die Ergebnisse synthetisiert werden.
|
||||
Die Decision Engine fungiert als zentraler Orchestrator für die Intent-Erkennung und das dynamische Retrieval-Routing. Sie bestimmt, wie das System auf eine Nutzeranfrage reagiert, welche Informationstypen bevorzugt werden und wie der Wissensgraph für die spezifische Situation verformt wird.
|
||||
|
||||
### 5.1 Intent Recognition: Hybrid-Routing (WP-25)
|
||||
Das System nutzt einen **Hybrid-Modus** mit Keyword Fast-Path und LLM Slow-Path:
|
||||
### 5.1 Intent Recognition: Dual-Path Routing
|
||||
Das System nutzt ein zweistufiges Verfahren, um die Absicht des Nutzers zu identifizieren:
|
||||
|
||||
1. **Fast Path (Keyword Trigger):** Das System scannt die Anfrage nach definierten `trigger_keywords`. Wird ein Treffer gefunden, wird die entsprechende Strategie sofort ohne LLM-Einsatz gewählt (z.B. "Soll ich" → `DECISION`).
|
||||
2. **Type Keywords:** Prüft `detection_keywords` aus `types.yaml` für Interview-Modus (z.B. "Projekt" + "neu" → `INTERVIEW`).
|
||||
3. **Slow Path (LLM Router):** Wenn kein Keyword matcht und `llm_fallback_enabled: true` gesetzt ist, analysiert ein LLM die Nachricht mittels Few-Shot Prompting (`intent_router_v1`).
|
||||
1. **Fast Path (Keyword Trigger):** Das System scannt die Anfrage nach definierten `trigger_keywords`. Wird ein Treffer gefunden, wird die entsprechende Strategie sofort ohne LLM-Einsatz gewählt.
|
||||
2. **Slow Path (LLM Router):** Wenn kein Keyword matcht und `llm_fallback_enabled: true` gesetzt ist, analysiert ein LLM die Nachricht mittels Few-Shot Prompting.
|
||||
|
||||
#### LLM Router Konfiguration
|
||||
Der Router nutzt den `llm_router_prompt`, um Anfragen in eine der fünf Kern-Strategien (`FACT`, `DECISION`, `EMPATHY`, `CODING`, `INTERVIEW`) zu klassifizieren.
|
||||
|
|
@ -250,471 +242,36 @@ Der Router nutzt den `llm_router_prompt`, um Anfragen in eine der fünf Kern-Str
|
|||
|
||||
---
|
||||
|
||||
### 5.2 Multi-Stream Konfiguration (WP-25)
|
||||
### 5.2 Strategie-Mechaniken (Graph Shaping)
|
||||
Jede Strategie definiert drei Hebel, um das Ergebnis des Retrievers zu beeinflussen:
|
||||
|
||||
Seit WP-25 nutzt die Decision Engine eine **Stream-Library** mit spezialisierten Wissens-Streams:
|
||||
|
||||
**Stream-Library (`streams_library`):**
|
||||
* **`values_stream`:** Identität, Ethik und Prinzipien (filter_types: `value`, `principle`, `belief`, `trait`, `boundary`, `need`, `motivation`)
|
||||
* **`facts_stream`:** Operative Daten (filter_types: `project`, `decision`, `task`, `goal`, `event`, `state`)
|
||||
* **`biography_stream`:** Persönliche Erfahrungen (filter_types: `experience`, `journal`, `profile`, `person`)
|
||||
* **`risk_stream`:** Hindernisse und Gefahren (filter_types: `risk`, `obstacle`, `bias`)
|
||||
* **`tech_stream`:** Technisches Wissen (filter_types: `concept`, `source`, `glossary`, `idea`, `insight`, `skill`, `habit`)
|
||||
|
||||
**Stream-Parameter:**
|
||||
* **`query_template`:** Transformiert die ursprüngliche Anfrage für spezialisierte Suche (z.B. "Welche meiner Werte und Prinzipien betreffen: {query}")
|
||||
* **`filter_types`:** Strikte Synchronisation mit `types.yaml` (v2.7.0)
|
||||
* **`top_k`:** Anzahl der Treffer pro Stream (z.B. 5 für Values, 3 für Risk)
|
||||
* **`edge_boosts`:** Individuelle Edge-Gewichtung pro Stream (z.B. `guides: 3.0` für Values Stream)
|
||||
|
||||
**Strategie-Komposition (`strategies`):**
|
||||
Jede Strategie definiert, welche Streams aktiviert werden:
|
||||
|
||||
* **`use_streams`:** Liste der Stream-Keys, die parallel abgefragt werden (z.B. `["values_stream", "facts_stream", "risk_stream"]` für `DECISION`)
|
||||
* **`prompt_template`:** Template-Key aus `prompts.yaml` für die Wissens-Synthese (z.B. `decision_synthesis_v1`)
|
||||
* **`prepend_instruction`:** Optional: Zusätzliche Anweisung für das LLM (z.B. "Analysiere die Fakten vor dem Hintergrund meiner Werte")
|
||||
* **`preferred_provider`:** Optional: Provider-Präferenz für diese Strategie (z.B. `gemini` für DECISION)
|
||||
|
||||
### 5.3 Strategie-Mechaniken (Graph Shaping)
|
||||
Jede Strategie definiert mehrere Hebel, um das Ergebnis zu beeinflussen:
|
||||
|
||||
* **`use_streams`:** Aktiviert parallele Wissens-Streams (WP-25).
|
||||
* **`inject_types`:** Erzwingt die Einbindung bestimmter Notiz-Typen (z. B. `value` bei Entscheidungen), auch wenn diese semantisch eine geringere Ähnlichkeit aufweisen.
|
||||
* **`edge_boosts`:** Erhöht die Gewichtung spezifischer Kanten-Typen in der Scoring-Formel. Dies ermöglicht es dem Graphen, die Textsuche situativ zu "überstimmen".
|
||||
* **`prepend_instruction`:** Injiziert eine spezifische Systemanweisung in das LLM-Prompt, um den Antwortstil anzupassen (z. B. "Wäge Fakten gegen Werte ab").
|
||||
|
||||
---
|
||||
|
||||
### 5.4 Übersicht der Strategien (WP-25)
|
||||
### 5.3 Übersicht der Strategien
|
||||
|
||||
| Strategie | Fokus | Aktive Streams | Bevorzugte Kanten (`edge_boosts`) |
|
||||
| Strategie | Fokus | Bevorzugte Kanten (`edge_boosts`) | Injektionstypen |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **FACT_WHAT** | Wissensabfrage & Listen | `facts_stream`, `tech_stream`, `biography_stream` | `part_of` (2.0), `depends_on` (1.5), `implemented_in` (1.5) |
|
||||
| **FACT_WHEN** | Zeitpunkte & Termine | `facts_stream`, `biography_stream`, `tech_stream` | `part_of` (2.0), `depends_on` (1.5) |
|
||||
| **DECISION** | Rat, Strategie & Abwägung | `values_stream`, `facts_stream`, `risk_stream` | `blocks` (2.5), `impacts` (2.0), `risk_of` (2.5) |
|
||||
| **EMPATHY** | Emotionale Resonanz | `biography_stream`, `values_stream` | `related_to` (1.5), `experienced_in` (2.0) |
|
||||
| **CODING** | Programmierung & Syntax | `tech_stream`, `facts_stream` | `uses` (2.5), `implemented_in` (3.0) |
|
||||
| **INTERVIEW** | Erfassung neuer Daten | *(Keine Streams)* | *(Keine)* |
|
||||
| **FACT** | Wissensabfrage & Definitionen | `part_of` (2.0), `composed_of` (2.0), `similar_to` (1.5) | *(Keine)* |
|
||||
| **DECISION** | Rat, Strategie & Abwägung | `blocks` (2.5), `solves` (2.0), `risk_of` (2.5) | `value`, `principle`, `goal`, `risk` |
|
||||
| **EMPATHY** | Emotionale Resonanz | `based_on` (2.0), `experienced_in` (2.5), `related_to` (2.0) | `experience`, `belief`, `profile` |
|
||||
| **CODING** | Programmierung & Syntax | `implemented_in` (3.0), `uses` (2.5), `depends_on` (2.0) | `snippet`, `reference`, `source` |
|
||||
| **INTERVIEW** | Erfassung neuer Daten | *(Keine)* | *(Keine)* |
|
||||
|
||||
---
|
||||
|
||||
### 5.5 Der Interview-Modus & Schemas
|
||||
### 5.4 Der Interview-Modus & Schemas
|
||||
Die Strategie `INTERVIEW` dient der strukturierten Datenerfassung.
|
||||
|
||||
* **Trigger:** Aktiviert durch Phrasen wie "neue notiz", "festhalten" oder "dokumentieren" (Type Keywords aus `types.yaml`).
|
||||
* **Trigger:** Aktiviert durch Phrasen wie "neue notiz", "festhalten" oder "dokumentieren".
|
||||
* **Schema-Logik:** Nutzt das `default`-Schema mit den Feldern `Titel`, `Thema/Inhalt` und `Tags`, sofern kein spezifisches Typ-Schema aus der `types.yaml` greift.
|
||||
* **Dynamik:** In diesem Modus wird der Fokus vom Retrieval (Wissen finden) auf die Extraktion (Wissen speichern) verschoben.
|
||||
* **Streams:** Keine Streams aktiviert (leere `use_streams` Liste).
|
||||
|
||||
> **Hinweis:** Da spezifische Schemas für Projekte oder Erfahrungen direkt in der `types.yaml` definiert werden, dient die `decision_engine.yaml` hier primär als Fallback für generische Datenaufnahmen.
|
||||
|
||||
### 5.6 Prompts-Konfiguration (`prompts.yaml` v3.2.2 - WP-25b)
|
||||
|
||||
Seit WP-25b nutzt MindNet eine **hierarchische Prompt-Struktur** mit Lazy-Loading. Prompts werden erst zur Laufzeit geladen, basierend auf dem exakt aktiven Modell.
|
||||
|
||||
**Hierarchische Template-Struktur:**
|
||||
```yaml
|
||||
decision_synthesis_v1:
|
||||
# Level 1: Modell-spezifisch (höchste Priorität)
|
||||
"google/gemini-2.0-flash-exp:free": |
|
||||
WERTE & PRINZIPIEN (Identität):
|
||||
{values_stream}
|
||||
|
||||
OPERATIVE FAKTEN (Realität):
|
||||
{facts_stream}
|
||||
|
||||
RISIKO-RADAR (Konsequenzen):
|
||||
{risk_stream}
|
||||
|
||||
ENTSCHEIDUNGSFRAGE:
|
||||
{query}
|
||||
Nutze deine hohe Reasoning-Kapazität für eine tiefe Synthese.
|
||||
|
||||
"meta-llama/llama-3.3-70b-instruct:free": |
|
||||
Erstelle eine fundierte Synthese für die Frage: "{query}"
|
||||
Nutze die Daten: {values_stream}, {facts_stream} und {risk_stream}.
|
||||
Trenne klare Fakten von Erfahrungen. Bleibe strikt beim bereitgestellten Kontext.
|
||||
|
||||
# Level 2: Provider-Fallback (mittlere Priorität)
|
||||
openrouter: |
|
||||
WERTE & PRINZIPIEN (Identität):
|
||||
{values_stream}
|
||||
|
||||
OPERATIVE FAKTEN (Realität):
|
||||
{facts_stream}
|
||||
|
||||
RISIKO-RADAR (Konsequenzen):
|
||||
{risk_stream}
|
||||
|
||||
ENTSCHEIDUNGSFRAGE:
|
||||
{query}
|
||||
|
||||
ollama: |
|
||||
WERTE & PRINZIPIEN (Identität):
|
||||
{values_stream}
|
||||
|
||||
OPERATIVE FAKTEN (Realität):
|
||||
{facts_stream}
|
||||
|
||||
RISIKO-RADAR (Konsequenzen):
|
||||
{risk_stream}
|
||||
|
||||
ENTSCHEIDUNGSFRAGE:
|
||||
{query}
|
||||
|
||||
# Level 3: Global Default (niedrigste Priorität)
|
||||
default: |
|
||||
Synthetisiere die folgenden Informationen für: {query}
|
||||
{values_stream} | {facts_stream} | {risk_stream}
|
||||
```
|
||||
|
||||
**Auflösungs-Logik:**
|
||||
1. **Level 1:** Exakte Modell-ID (z.B. `google/gemini-2.0-flash-exp:free`)
|
||||
2. **Level 2:** Provider-Fallback (z.B. `openrouter`, `ollama`, `gemini`)
|
||||
3. **Level 3:** Global Default (`default` → `gemini` → `ollama` → `""`)
|
||||
|
||||
**Lazy-Loading:**
|
||||
* Prompts werden erst zur Laufzeit geladen, wenn das aktive Modell bekannt ist
|
||||
* **Parameter:** `prompt_key` und `variables` statt vorformatierter Strings
|
||||
* **Vorteil:** Maximale Resilienz bei Modell-Fallbacks (Cloud → Local)
|
||||
|
||||
**Pre-Initialization:**
|
||||
Alle möglichen Stream-Variablen werden vorab initialisiert (verhindert KeyErrors bei unvollständigen Konfigurationen).
|
||||
|
||||
**PROMPT-TRACE Logging:**
|
||||
Das System protokolliert die genutzte Auflösungs-Ebene:
|
||||
* `🎯 [PROMPT-TRACE] Level 1 Match: Model-specific`
|
||||
* `📡 [PROMPT-TRACE] Level 2 Match: Provider-fallback`
|
||||
* `⚓ [PROMPT-TRACE] Level 3 Match: Global Default`
|
||||
|
||||
---
|
||||
|
||||
## 6. LLM Profile Registry (`llm_profiles.yaml` v1.3.0)
|
||||
|
||||
Seit WP-25a nutzt MindNet eine **Mixture of Experts (MoE)** Architektur mit profilbasierter Experten-Steuerung. Jede Systemaufgabe (Synthese, Ingestion-Validierung, Routing, Kompression) wird einem dedizierten Profil zugewiesen, das Modell, Provider und Parameter unabhängig von der globalen Konfiguration definiert.
|
||||
|
||||
### 6.1 Profil-Struktur
|
||||
|
||||
Jedes Profil definiert:
|
||||
* **`provider`:** Cloud-Provider (`openrouter`, `gemini`, `ollama`)
|
||||
* **`model`:** Spezifisches Modell (z.B. `mistralai/mistral-7b-instruct:free`)
|
||||
* **`temperature`:** Kreativität/Determinismus (0.0 = deterministisch, 1.0 = kreativ)
|
||||
* **`fallback_profile`:** Optional: Name des Fallback-Profils bei Fehlern
|
||||
* **`dimensions`:** Optional: Für Embedding-Profile (z.B. 768 für nomic-embed-text)
|
||||
|
||||
**Beispiel:**
|
||||
```yaml
|
||||
synthesis_pro:
|
||||
provider: "openrouter"
|
||||
model: "gemini-1.5-mistralai/mistral-7b-instruct:free"
|
||||
temperature: 0.7
|
||||
fallback_profile: "synthesis_backup"
|
||||
```
|
||||
|
||||
### 6.2 Verfügbare Experten-Profile
|
||||
|
||||
| Profil | Provider | Modell | Temperature | Fallback | Zweck |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| **`synthesis_pro`** | openrouter | gemini-1.5-mistralai/mistral-7b-instruct:free | 0.7 | `synthesis_backup` | Hochwertige Synthese (Chat-Antworten) |
|
||||
| **`synthesis_backup`** | openrouter | mistralai/mistral-large | 0.5 | `identity_safe` | Backup-Cloud-Experte (Resilienz) |
|
||||
| **`tech_expert`** | openrouter | anthropic/claude-3.5-sonnet | 0.3 | `synthesis_pro` | Fachspezialist für Code & Technik |
|
||||
| **`compression_fast`** | openrouter | mistralai/mistral-7b-instruct:free | 0.1 | `identity_safe` | Schnelle Kompression & Routing |
|
||||
| **`ingest_extractor`** | openrouter | mistralai/mistral-7b-instruct:free | 0.2 | `synthesis_backup` | Extraktion komplexer Datenstrukturen |
|
||||
| **`ingest_validator`** | openrouter | mistralai/mistral-7b-instruct:free | 0.0 | `compression_fast` | Binäre Prüfungen (YES/NO, deterministisch) |
|
||||
| **`identity_safe`** | ollama | phi3:mini | 0.2 | *(kein Fallback)* | Lokaler Anker & Privacy (terminaler Endpunkt) |
|
||||
| **`embedding_expert`** | ollama | nomic-embed-text | - | - | Embedding-Modell (dimensions: 768) |
|
||||
|
||||
### 6.3 Fallback-Kaskade (WP-25a)
|
||||
|
||||
Die Profile implementieren eine **rekursive Fallback-Kaskade**:
|
||||
|
||||
1. **Primäres Profil:** System versucht das angeforderte Profil (z.B. `synthesis_pro`)
|
||||
2. **Fallback-Level 1:** Bei Fehler → `fallback_profile` (z.B. `synthesis_backup`)
|
||||
3. **Fallback-Level 2:** Bei weiterem Fehler → nächster Fallback (z.B. `identity_safe`)
|
||||
4. **Terminaler Endpunkt:** `identity_safe` hat keinen Fallback (lokales Modell als letzte Instanz)
|
||||
|
||||
**Schutzmechanismen:**
|
||||
* **Zirkuläre Referenzen:** `visited_profiles`-Tracking verhindert Endlosschleifen
|
||||
* **Background-Semaphore:** Parallele Tasks werden gedrosselt (konfigurierbar via `BACKGROUND_LIMIT`)
|
||||
|
||||
### 6.4 Integration in Decision Engine
|
||||
|
||||
Die `decision_engine.yaml` referenziert Profile über:
|
||||
* **`router_profile`:** Profil für Intent-Erkennung (z.B. `compression_fast`)
|
||||
* **`llm_profile`:** Profil für Strategie-spezifische Synthese (z.B. `tech_expert` für CODING)
|
||||
* **`compression_profile`:** Profil für Stream-Kompression (z.B. `compression_fast`)
|
||||
|
||||
**Stream-Konfiguration:**
|
||||
```yaml
|
||||
values_stream:
|
||||
llm_profile: "identity_safe" # Lokales Modell für Privacy
|
||||
compression_profile: "identity_safe"
|
||||
compression_threshold: 2500
|
||||
```
|
||||
|
||||
### 6.5 Environment-Variablen
|
||||
|
||||
| Variable | Default | Beschreibung |
|
||||
| :--- | :--- | :--- |
|
||||
| `MINDNET_LLM_PROFILES_PATH` | `config/llm_profiles.yaml` | Pfad zur Profil-Registry |
|
||||
|
||||
**Hinweis:** Die `.env` Variablen `MINDNET_LLM_PROVIDER`, `MINDNET_LLM_MODEL` etc. dienen nur noch als Fallback, wenn kein Profil angegeben wird.
|
||||
|
||||
---
|
||||
|
||||
## 7. Konfigurations-Verbindungen & Datenfluss
|
||||
|
||||
Die vier zentralen Konfigurationsdateien (`types.yaml`, `decision_engine.yaml`, `llm_profiles.yaml`, `prompts.yaml`) arbeiten eng zusammen, um das agentische Multi-Stream RAG System zu steuern. Diese Sektion erklärt die Verbindungen und zeigt einen konkreten Praxisablauf.
|
||||
|
||||
### 7.1 Architektur-Übersicht
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "1. Typ-Definition (types.yaml)"
|
||||
T1[Typ: value<br/>chunking_profile: structured_strict<br/>retriever_weight: 1.00]
|
||||
T2[Typ: risk<br/>chunking_profile: sliding_short<br/>retriever_weight: 0.85]
|
||||
T3[Typ: project<br/>chunking_profile: sliding_smart_edges<br/>retriever_weight: 0.97]
|
||||
end
|
||||
|
||||
subgraph "2. Stream-Konfiguration (decision_engine.yaml)"
|
||||
D1[values_stream<br/>filter_types: value, principle, belief...<br/>llm_profile: identity_safe<br/>compression_profile: identity_safe]
|
||||
D2[risk_stream<br/>filter_types: risk, obstacle, bias<br/>llm_profile: synthesis_pro<br/>compression_profile: compression_fast]
|
||||
D3[facts_stream<br/>filter_types: project, decision, task...<br/>llm_profile: synthesis_pro<br/>compression_profile: compression_fast]
|
||||
end
|
||||
|
||||
subgraph "3. Strategie-Komposition (decision_engine.yaml)"
|
||||
S1[DECISION Strategie<br/>use_streams: values_stream, facts_stream, risk_stream<br/>llm_profile: synthesis_pro<br/>prompt_template: decision_synthesis_v1]
|
||||
end
|
||||
|
||||
subgraph "4. Experten-Profile (llm_profiles.yaml)"
|
||||
P1[synthesis_pro<br/>provider: openrouter<br/>model: google/gemini-2.0-flash-exp:free<br/>temperature: 0.7<br/>fallback_profile: synthesis_backup]
|
||||
P2[compression_fast<br/>provider: openrouter<br/>model: mistralai/mistral-7b-instruct:free<br/>temperature: 0.1<br/>fallback_profile: identity_safe]
|
||||
P3[identity_safe<br/>provider: ollama<br/>model: phi3:mini<br/>temperature: 0.2<br/>fallback_profile: null]
|
||||
end
|
||||
|
||||
subgraph "5. Prompt-Templates (prompts.yaml)"
|
||||
PR1[decision_synthesis_v1<br/>Level 1: google/gemini-2.0-flash-exp:free<br/>Level 2: openrouter<br/>Level 3: default]
|
||||
end
|
||||
|
||||
T1 -->|filter_types| D1
|
||||
T2 -->|filter_types| D2
|
||||
T3 -->|filter_types| D3
|
||||
|
||||
D1 -->|use_streams| S1
|
||||
D2 -->|use_streams| S1
|
||||
D3 -->|use_streams| S1
|
||||
|
||||
S1 -->|llm_profile| P1
|
||||
D1 -->|llm_profile| P3
|
||||
D2 -->|compression_profile| P2
|
||||
D3 -->|compression_profile| P2
|
||||
|
||||
S1 -->|prompt_template| PR1
|
||||
P1 -->|model lookup| PR1
|
||||
|
||||
style T1 fill:#e1f5ff
|
||||
style T2 fill:#e1f5ff
|
||||
style T3 fill:#e1f5ff
|
||||
style D1 fill:#fff4e1
|
||||
style D2 fill:#fff4e1
|
||||
style D3 fill:#fff4e1
|
||||
style S1 fill:#ffe1f5
|
||||
style P1 fill:#e1ffe1
|
||||
style P2 fill:#e1ffe1
|
||||
style P3 fill:#e1ffe1
|
||||
style PR1 fill:#f5e1ff
|
||||
```
|
||||
|
||||
### 7.2 Verbindungs-Matrix
|
||||
|
||||
| Von | Zu | Verbindung | Beschreibung |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **`types.yaml`** | **`decision_engine.yaml`** | `filter_types` | Streams filtern Notizen basierend auf Typen aus `types.yaml`. Die Liste `filter_types: ["value", "principle", "belief"]` muss exakt den Typ-Namen aus `types.yaml` entsprechen. |
|
||||
| **`types.yaml`** | **`decision_engine.yaml`** | `detection_keywords` | Keywords aus `types.yaml` werden für den Interview-Modus verwendet (z.B. "Projekt" + "neu" → `INTERVIEW`). |
|
||||
| **`decision_engine.yaml`** | **`llm_profiles.yaml`** | `router_profile` | Intent-Erkennung nutzt das Profil `compression_fast` für schnelle Klassifizierung. |
|
||||
| **`decision_engine.yaml`** | **`llm_profiles.yaml`** | `llm_profile` (Stream) | Jeder Stream definiert sein eigenes Profil für Retrieval und Kompression (z.B. `identity_safe` für Privacy). |
|
||||
| **`decision_engine.yaml`** | **`llm_profiles.yaml`** | `llm_profile` (Strategie) | Die finale Synthese nutzt das Strategie-Profil (z.B. `synthesis_pro` für DECISION). |
|
||||
| **`decision_engine.yaml`** | **`llm_profiles.yaml`** | `compression_profile` | Überlange Streams werden via `compression_profile` verdichtet (z.B. `compression_fast`). |
|
||||
| **`decision_engine.yaml`** | **`prompts.yaml`** | `prompt_template` | Strategien referenzieren Template-Keys (z.B. `decision_synthesis_v1`). |
|
||||
| **`llm_profiles.yaml`** | **`prompts.yaml`** | Hierarchische Auflösung | Das aktive Modell aus dem Profil bestimmt, welcher Prompt-Level geladen wird (Model-ID → Provider → Default). |
|
||||
| **`llm_profiles.yaml`** | **`llm_profiles.yaml`** | `fallback_profile` | Rekursive Fallback-Kaskade bei Fehlern (z.B. `synthesis_pro` → `synthesis_backup` → `identity_safe`). |
|
||||
|
||||
### 7.3 Praxisbeispiel: DECISION-Anfrage
|
||||
|
||||
**User-Anfrage:** `"Soll ich das neue Projekt starten?"`
|
||||
|
||||
#### Schritt 1: Intent-Erkennung
|
||||
|
||||
**Datei:** `decision_engine.yaml`
|
||||
```yaml
|
||||
settings:
|
||||
router_profile: "compression_fast" # → llm_profiles.yaml
|
||||
router_prompt_key: "intent_router_v1" # → prompts.yaml
|
||||
```
|
||||
|
||||
**Ablauf:**
|
||||
1. System prüft `trigger_keywords` in `DECISION` Strategie → findet `"soll ich"` → **Intent: DECISION**
|
||||
2. Falls kein Keyword-Match: LLM-Router nutzt `compression_fast` Profil aus `llm_profiles.yaml`
|
||||
3. Router lädt `intent_router_v1` aus `prompts.yaml` (hierarchisch basierend auf aktivem Modell)
|
||||
|
||||
#### Schritt 2: Stream-Aktivierung
|
||||
|
||||
**Datei:** `decision_engine.yaml`
|
||||
```yaml
|
||||
strategies:
|
||||
DECISION:
|
||||
use_streams: ["values_stream", "facts_stream", "risk_stream"]
|
||||
llm_profile: "synthesis_pro" # → llm_profiles.yaml
|
||||
prompt_template: "decision_synthesis_v1" # → prompts.yaml
|
||||
```
|
||||
|
||||
**Ablauf:**
|
||||
1. System aktiviert drei parallele Streams: `values_stream`, `facts_stream`, `risk_stream`
|
||||
|
||||
#### Schritt 3: Stream-Konfiguration & Typ-Filterung
|
||||
|
||||
**Datei:** `decision_engine.yaml` (Streams) + `types.yaml` (Typ-Definitionen)
|
||||
|
||||
```yaml
|
||||
# decision_engine.yaml
|
||||
values_stream:
|
||||
filter_types: ["value", "principle", "belief", "trait", "boundary", "need", "motivation"]
|
||||
llm_profile: "identity_safe" # → llm_profiles.yaml
|
||||
compression_profile: "identity_safe" # → llm_profiles.yaml
|
||||
query_template: "Welche meiner Werte und Prinzipien betreffen: {query}"
|
||||
|
||||
facts_stream:
|
||||
filter_types: ["project", "decision", "task", "goal", "event", "state"]
|
||||
llm_profile: "synthesis_pro" # → llm_profiles.yaml
|
||||
compression_profile: "compression_fast" # → llm_profiles.yaml
|
||||
query_template: "Status, Ressourcen und Fakten zu: {query}"
|
||||
|
||||
risk_stream:
|
||||
filter_types: ["risk", "obstacle", "bias"]
|
||||
llm_profile: "synthesis_pro" # → llm_profiles.yaml
|
||||
compression_profile: "compression_fast" # → llm_profiles.yaml
|
||||
query_template: "Gefahren, Hindernisse oder Risiken bei: {query}"
|
||||
```
|
||||
|
||||
**Ablauf:**
|
||||
1. **Values Stream:** Sucht in Qdrant nach Notizen mit `type IN ["value", "principle", "belief", ...]` (definiert in `types.yaml`)
|
||||
2. **Facts Stream:** Sucht nach Notizen mit `type IN ["project", "decision", "task", ...]` (definiert in `types.yaml`)
|
||||
3. **Risk Stream:** Sucht nach Notizen mit `type IN ["risk", "obstacle", "bias"]` (definiert in `types.yaml`)
|
||||
|
||||
#### Schritt 4: Profil-Auflösung & Modell-Auswahl
|
||||
|
||||
**Datei:** `llm_profiles.yaml`
|
||||
|
||||
```yaml
|
||||
synthesis_pro:
|
||||
provider: "openrouter"
|
||||
model: "google/gemini-2.0-flash-exp:free"
|
||||
temperature: 0.7
|
||||
fallback_profile: "synthesis_backup" # → Rekursiver Fallback
|
||||
|
||||
compression_fast:
|
||||
provider: "openrouter"
|
||||
model: "mistralai/mistral-7b-instruct:free"
|
||||
temperature: 0.1
|
||||
fallback_profile: "identity_safe"
|
||||
|
||||
identity_safe:
|
||||
provider: "ollama"
|
||||
model: "phi3:mini"
|
||||
temperature: 0.2
|
||||
fallback_profile: null # Terminaler Endpunkt
|
||||
```
|
||||
|
||||
**Ablauf:**
|
||||
1. **Values Stream:** Nutzt `identity_safe` → Ollama/phi3:mini (lokal, Privacy)
|
||||
2. **Facts Stream:** Nutzt `synthesis_pro` → OpenRouter/Gemini 2.0 (Cloud)
|
||||
3. **Risk Stream:** Nutzt `synthesis_pro` → OpenRouter/Gemini 2.0 (Cloud)
|
||||
4. **Kompression:** Falls Stream > `compression_threshold`, nutzt `compression_fast` → OpenRouter/Mistral 7B
|
||||
|
||||
#### Schritt 5: Prompt-Loading (Hierarchische Auflösung)
|
||||
|
||||
**Datei:** `prompts.yaml`
|
||||
|
||||
```yaml
|
||||
decision_synthesis_v1:
|
||||
# Level 1: Modell-spezifisch (höchste Priorität)
|
||||
"google/gemini-2.0-flash-exp:free": |
|
||||
Agiere als strategischer Partner für: {query}
|
||||
WERTE: {values_stream} | FAKTEN: {facts_stream} | RISIKEN: {risk_stream}
|
||||
Prüfe die Fakten gegen meine Werte. Zeige Zielkonflikte auf. Gib eine klare Empfehlung.
|
||||
|
||||
# Level 2: Provider-Fallback
|
||||
openrouter: |
|
||||
Strategische Multi-Stream Analyse für: {query}
|
||||
Werte-Basis: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}
|
||||
Bitte wäge ab und gib eine Empfehlung.
|
||||
|
||||
# Level 3: Global Default
|
||||
default: "Prüfe {query} gegen Werte {values_stream} und Fakten {facts_stream}."
|
||||
```
|
||||
|
||||
**Ablauf:**
|
||||
1. System hat `synthesis_pro` Profil geladen → Modell: `google/gemini-2.0-flash-exp:free`
|
||||
2. System sucht in `prompts.yaml` nach `decision_synthesis_v1`:
|
||||
- **Level 1:** Findet exakten Match für `google/gemini-2.0-flash-exp:free` → **Verwendet diesen Prompt**
|
||||
- Falls nicht gefunden: **Level 2** → `openrouter` Fallback
|
||||
- Falls nicht gefunden: **Level 3** → `default` Fallback
|
||||
3. Prompt wird mit Stream-Variablen formatiert: `{values_stream}`, `{facts_stream}`, `{risk_stream}`, `{query}`
|
||||
|
||||
#### Schritt 6: Finale Synthese
|
||||
|
||||
**Ablauf:**
|
||||
1. System ruft LLM auf mit:
|
||||
- **Profil:** `synthesis_pro` (OpenRouter/Gemini 2.0, Temperature 0.7)
|
||||
- **Prompt:** Level-1 Template aus `prompts.yaml` (modell-spezifisch optimiert)
|
||||
- **Variablen:** Formatierte Stream-Inhalte
|
||||
2. Falls Fehler (z.B. Rate-Limit 429):
|
||||
- **Fallback:** `synthesis_backup` (Llama 3.3)
|
||||
- **Prompt:** Automatisch Level-2 (`openrouter`) oder Level-3 (`default`) geladen
|
||||
3. Antwort wird an User zurückgegeben
|
||||
|
||||
### 7.4 Konfigurations-Synchronisation Checkliste
|
||||
|
||||
Beim Ändern einer Konfigurationsdatei müssen folgende Abhängigkeiten geprüft werden:
|
||||
|
||||
**✅ `types.yaml` ändern:**
|
||||
- [ ] Prüfe, ob `filter_types` in `decision_engine.yaml` Streams noch gültig sind
|
||||
- [ ] Prüfe, ob `detection_keywords` für Interview-Modus noch passen
|
||||
- [ ] Prüfe, ob `chunking_profile` noch existiert (in `types.yaml` definiert)
|
||||
|
||||
**✅ `decision_engine.yaml` ändern:**
|
||||
- [ ] Prüfe, ob alle `filter_types` in Streams existieren in `types.yaml`
|
||||
- [ ] Prüfe, ob alle `llm_profile` / `compression_profile` existieren in `llm_profiles.yaml`
|
||||
- [ ] Prüfe, ob alle `prompt_template` Keys existieren in `prompts.yaml`
|
||||
|
||||
**✅ `llm_profiles.yaml` ändern:**
|
||||
- [ ] Prüfe, ob `fallback_profile` Referenzen zirkulär sind (Schutz: `visited_profiles`)
|
||||
- [ ] Prüfe, ob alle referenzierten Profile existieren
|
||||
- [ ] Prüfe, ob Modell-IDs mit `prompts.yaml` Level-1 Keys übereinstimmen (optional, aber empfohlen)
|
||||
|
||||
**✅ `prompts.yaml` ändern:**
|
||||
- [ ] Prüfe, ob alle `prompt_template` Keys aus `decision_engine.yaml` existieren
|
||||
- [ ] Prüfe, ob Modell-spezifische Keys (Level 1) mit `llm_profiles.yaml` Modell-IDs übereinstimmen
|
||||
- [ ] Prüfe, ob alle Stream-Variablen (`{values_stream}`, `{facts_stream}`, etc.) initialisiert werden
|
||||
|
||||
### 7.5 Debugging-Tipps
|
||||
|
||||
**Problem:** Stream findet keine Notizen
|
||||
- **Prüfung:** `filter_types` in Stream stimmt mit Typ-Namen in `types.yaml` überein? (Case-sensitive!)
|
||||
- **Prüfung:** Existieren Notizen mit diesen Typen im Vault?
|
||||
|
||||
**Problem:** Falsches Modell wird verwendet
|
||||
- **Prüfung:** `llm_profile` in Stream/Strategie existiert in `llm_profiles.yaml`?
|
||||
- **Prüfung:** `fallback_profile` Kaskade führt zu unerwartetem Modell?
|
||||
|
||||
**Problem:** Prompt wird nicht gefunden
|
||||
- **Prüfung:** `prompt_template` Key existiert in `prompts.yaml`?
|
||||
- **Prüfung:** Hierarchische Auflösung (Level 1 → 2 → 3) funktioniert? (Logs: `[PROMPT-TRACE]`)
|
||||
|
||||
**Problem:** Kompression wird nicht ausgelöst
|
||||
- **Prüfung:** `compression_threshold` in Stream-Konfiguration gesetzt?
|
||||
- **Prüfung:** `compression_profile` existiert in `llm_profiles.yaml`?
|
||||
|
||||
---
|
||||
|
||||
Auszug aus der decision_engine.yaml
|
||||
```yaml
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
doc_type: technical_reference
|
||||
audience: developer, architect
|
||||
scope: database, qdrant, schema, agentic_validation
|
||||
scope: database, qdrant, schema
|
||||
status: active
|
||||
version: 4.5.8
|
||||
context: "Exakte Definition der Datenmodelle (Payloads) in Qdrant und Index-Anforderungen. Berücksichtigt WP-14 Modularisierung, WP-15b Multi-Hashes und WP-24c Phase 3 Agentic Edge Validation (candidate: Präfix, verified Status)."
|
||||
version: 2.8.0
|
||||
context: "Exakte Definition der Datenmodelle (Payloads) in Qdrant und Index-Anforderungen. Berücksichtigt WP-14 Modularisierung und WP-15b Multi-Hashes."
|
||||
---
|
||||
|
||||
# Technisches Datenmodell (Qdrant Schema)
|
||||
|
|
@ -96,61 +96,30 @@ Es müssen Payload-Indizes für folgende Felder existieren:
|
|||
|
||||
## 4. Edge Payload (`mindnet_edges`)
|
||||
|
||||
Gerichtete Kanten zwischen Knoten. Stark erweitert in v2.6 für Provenienz-Tracking. Seit v2.9.1 unterstützt das System **Section-basierte Links** (`[[Note#Section]]`), die in `target_id` und `target_section` aufgeteilt werden.
|
||||
Gerichtete Kanten zwischen Knoten. Stark erweitert in v2.6 für Provenienz-Tracking.
|
||||
|
||||
**JSON-Schema:**
|
||||
|
||||
```json
|
||||
{
|
||||
"edge_id": "string (keyword)", // Deterministischer Hash aus (src, dst, kind, variant)
|
||||
// variant = target_section (erlaubt Multigraph für Sections)
|
||||
"edge_id": "string (keyword)", // Deterministischer Hash aus (src, dst, kind)
|
||||
"source_id": "string (keyword)", // Chunk-ID (Start)
|
||||
"target_id": "string (keyword)", // Chunk-ID oder Note-Titel (bei Unresolved)
|
||||
// WICHTIG: Enthält NUR den Note-Namen, KEINE Section-Info
|
||||
"target_section": "string (keyword)", // Optional: Abschnitts-Name (z.B. "P3 – Disziplin")
|
||||
// Wird aus [[Note#Section]] extrahiert
|
||||
"kind": "string (keyword)", // Beziehungsart (z.B. 'depends_on')
|
||||
"scope": "string (keyword)", // Immer 'chunk' (Legacy-Support: 'note')
|
||||
"note_id": "string (keyword)", // Owner Note ID (Ursprung der Kante)
|
||||
|
||||
// Provenance & Quality (WP03/WP15/WP-24c)
|
||||
"provenance": "keyword", // 'explicit', 'explicit:note_zone', 'explicit:callout', 'rule', 'semantic_ai', 'structure', 'candidate:...' (vor Phase 3)
|
||||
"rule_id": "string (keyword)", // Traceability: 'inline:rel', 'explicit:wikilink', 'candidate:...' (vor Phase 3), 'explicit' (nach Phase 3 VERIFIED)
|
||||
"confidence": "float", // Vertrauenswürdigkeit (0.0 - 1.0)
|
||||
"scope": "string (keyword)", // 'chunk' (Standard) oder 'note' (Note-Scope Zonen) - WP-24c v4.2.0
|
||||
"virtual": "boolean (optional)" // true für automatisch generierte Spiegelkanten (Invers-Logik) - WP-24c v4.5.8
|
||||
// Provenance & Quality (WP03/WP15)
|
||||
"provenance": "keyword", // 'explicit', 'rule', 'smart', 'structure'
|
||||
"rule_id": "string (keyword)", // Traceability: 'inline:rel', 'explicit:wikilink', 'smart:llm'
|
||||
"confidence": "float" // Vertrauenswürdigkeit (0.0 - 1.0)
|
||||
}
|
||||
```
|
||||
|
||||
**Section-Support (WP-15c):**
|
||||
* Links wie `[[Note#Section]]` werden in `target_id="Note"` und `target_section="Section"` aufgeteilt.
|
||||
* **Self-Links:** `[[#Section]]` wird zu `target_id="current_note_id"` und `target_section="Section"` aufgelöst.
|
||||
* Die Edge-ID enthält die Section als `variant`, sodass mehrere Kanten zwischen denselben Knoten existieren können, wenn sie auf verschiedene Sections zeigen (Multigraph-Modus).
|
||||
* Semantische Deduplizierung basiert auf `src->tgt:kind@sec` Key, um "Phantom-Knoten" zu vermeiden.
|
||||
* **Metadaten-Persistenz:** `target_section`, `provenance` und `confidence` werden durchgängig im In-Memory Subgraph und Datenbank-Adapter erhalten.
|
||||
|
||||
**Phase 3 Validierung (WP-24c v4.5.8):**
|
||||
* **candidate: Präfix:** Kanten mit `candidate:` in `rule_id` oder `provenance` durchlaufen Phase 3 Validierung
|
||||
* **Vor Validierung:** `provenance: "candidate:global_pool"` oder `rule_id: "candidate:..."`
|
||||
* **Nach VERIFIED:** `candidate:` Präfix wird entfernt, Kante wird persistiert
|
||||
* **Nach REJECTED:** Kante wird **nicht** in die Datenbank geschrieben (verhindert "Geister-Verknüpfungen")
|
||||
* **Wichtig:** Nur Kanten ohne `candidate:` Präfix werden im Graph persistiert
|
||||
|
||||
**Note-Scope vs. Chunk-Scope (WP-24c v4.2.0):**
|
||||
* **Chunk-Scope (`scope: "chunk"`):** Standard, `source_id = chunk_id` (z.B. `note-id#c00`)
|
||||
* **Note-Scope (`scope: "note"`):** Aus Note-Scope Zonen, `source_id = note_id` (nicht `chunk_id`)
|
||||
* **Phase 3 Kontext-Optimierung:** Note-Scope nutzt `note_summary`/`note_text`, Chunk-Scope nutzt spezifischen Chunk-Text
|
||||
|
||||
**Automatische Spiegelkanten (WP-24c v4.5.8):**
|
||||
* **virtual: true:** Markiert automatisch generierte Invers-Kanten (Spiegelkanten)
|
||||
* **Provenance:** `structure` (System-generiert, geschützt durch Provenance Firewall)
|
||||
* **Confidence:** Leicht gedämpft (`original * 0.9`) im Vergleich zu expliziten Kanten
|
||||
|
||||
**Erforderliche Indizes:**
|
||||
Es müssen Payload-Indizes für folgende Felder existieren:
|
||||
* `source_id`
|
||||
* `target_id`
|
||||
* `target_section` (neu: Keyword-Index für Section-basierte Filterung)
|
||||
* `kind`
|
||||
* `scope`
|
||||
* `note_id`
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ doc_type: technical_reference
|
|||
audience: developer, frontend_architect
|
||||
scope: architecture, graph_viz, state_management
|
||||
status: active
|
||||
version: 2.9.1
|
||||
version: 2.7.0
|
||||
context: "Technische Dokumentation des modularen Streamlit-Frontends, der Graph-Engines und des Editors."
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
doc_type: technical_reference
|
||||
audience: developer, devops
|
||||
scope: backend, ingestion, smart_edges, edge_registry, modularization, moe, lazy_prompts, agentic_validation
|
||||
scope: backend, ingestion, smart_edges, edge_registry, modularization
|
||||
status: active
|
||||
version: 4.5.8
|
||||
context: "Detaillierte technische Beschreibung der Import-Pipeline, Two-Pass-Workflow (WP-15b), modularer Datenbank-Architektur (WP-14), WP-25a profilgesteuerte Validierung, WP-25b Lazy-Prompt-Orchestration und WP-24c Phase 3 Agentic Edge Validation (v4.5.8). Integriert Mistral-safe Parsing und Deep Fallback."
|
||||
version: 2.9.0
|
||||
context: "Detaillierte technische Beschreibung der Import-Pipeline, Two-Pass-Workflow (WP-15b) und modularer Datenbank-Architektur (WP-14). Integriert Mistral-safe Parsing und Deep Fallback."
|
||||
---
|
||||
|
||||
# Ingestion Pipeline & Smart Processing
|
||||
|
|
@ -15,9 +15,9 @@ Die Ingestion transformiert Markdown in den Graphen. Entrypoint: `scripts/import
|
|||
|
||||
|
||||
|
||||
## 1. Der Import-Prozess (17-Schritte-Workflow - 3-Phasen-Modell)
|
||||
## 1. Der Import-Prozess (16-Schritte-Workflow)
|
||||
|
||||
Der Prozess ist **asynchron**, **idempotent** und wird nun in **drei logische Phasen** unterteilt, um die semantische Genauigkeit zu maximieren und die Graph-Qualität durch agentische Validierung zu sichern.
|
||||
Der Prozess ist **asynchron**, **idempotent** und wird nun in zwei logische Durchläufe (Passes) unterteilt, um die semantische Genauigkeit zu maximieren.
|
||||
|
||||
### Phase 1: Pre-Scan & Context (Pass 1)
|
||||
1. **Trigger & Async Dispatch:**
|
||||
|
|
@ -31,10 +31,9 @@ Der Prozess ist **asynchron**, **idempotent** und wird nun in **drei logische Ph
|
|||
4. **Edge Registry Initialisierung (WP-22):**
|
||||
* Laden der Singleton-Instanz der `EdgeRegistry`.
|
||||
* Validierung der Vokabular-Datei unter `MINDNET_VOCAB_PATH`.
|
||||
5. **Config Resolution (WP-14 / v2.13.12):**
|
||||
5. **Config Resolution (WP-14):**
|
||||
* Bestimmung von `chunking_profile` und `retriever_weight` via zentraler `TypeRegistry`.
|
||||
* **Priorität:** 1. Frontmatter (Override) -> 2. `types.yaml` (Type) -> 3. Global Default.
|
||||
* **Registry-First Profiling:** Automatische Anwendung der korrekten Profile basierend auf dem Note-Typ (z.B. `value` nutzt automatisch `structured_smart_edges_strict`).
|
||||
6. **LocalBatchCache & Summary Generation (WP-15b):**
|
||||
* Erstellung von Kurz-Zusammenfassungen für jede Note.
|
||||
* Speicherung im `batch_cache` als Referenzrahmen für die spätere Kantenvalidierung.
|
||||
|
|
@ -50,63 +49,24 @@ Der Prozess ist **asynchron**, **idempotent** und wird nun in **drei logische Ph
|
|||
* Bei Änderungen löscht `purge_artifacts()` via `app.core.ingestion.ingestion_db` alle alten Chunks und Edges der Note.
|
||||
* Die Namensauflösung erfolgt nun über das modularisierte `database`-Paket.
|
||||
10. **Chunking anwenden:** Zerlegung des Textes basierend auf dem ermittelten Profil (siehe Kap. 3).
|
||||
11. **Smart Edge Allocation & Kandidaten-Erzeugung (WP-15b / WP-25a / WP-25b):**
|
||||
11. **Smart Edge Allocation & Semantic Validation (WP-15b):**
|
||||
* Der `SemanticAnalyzer` schlägt Kanten-Kandidaten vor.
|
||||
* **Kandidaten-Markierung:** Alle vorgeschlagenen Kanten erhalten `candidate:` Präfix in `rule_id` oder `provenance`.
|
||||
* **Hinweis:** Die eigentliche LLM-Validierung erfolgt erst in **Phase 3** (siehe Schritt 17).
|
||||
* **Validierung:** Jeder Kandidat wird durch das LLM semantisch gegen das Ziel im **LocalBatchCache** geprüft.
|
||||
* **Traffic Control:** Nutzung der neutralen `clean_llm_text` Funktion zur Bereinigung von Steuerzeichen (<s>, [OUT]).
|
||||
* **Deep Fallback (v2.11.14):** Erkennt "Silent Refusals". Liefert die Cloud keine verwertbaren Kanten, wird ein lokaler Fallback via Ollama erzwungen.
|
||||
12. **Inline-Kanten finden:** Parsing von `[[rel:...]]` und Callouts.
|
||||
13. **Alias-Auflösung & Kanonisierung (WP-22):**
|
||||
* Jede Kante wird via `EdgeRegistry` normalisiert (z.B. `basiert_auf` -> `based_on`).
|
||||
* Unbekannte Typen werden in `unknown_edges.jsonl` protokolliert.
|
||||
14. **Default- & Strukturkanten:** Anwendung der `edge_defaults` und Erzeugung von Systemkanten (`belongs_to`, `next`, `prev`).
|
||||
15. **Embedding (Async - WP-25a):** Generierung der Vektoren via `embedding_expert` Profil aus `llm_profiles.yaml`.
|
||||
* **Profil-Auflösung:** Das `EmbeddingsClient` lädt Modell und Dimensionen direkt aus dem Profil (z.B. `nomic-embed-text`, 768 Dimensionen).
|
||||
* **Konsolidierung:** Entfernung der Embedding-Konfiguration aus der `.env` zugunsten zentraler Profil-Registry.
|
||||
|
||||
### Phase 3: Agentic Edge Validation (WP-24c v4.5.8)
|
||||
|
||||
17. **Finales Validierungs-Gate für candidate: Kanten:**
|
||||
* **Trigger-Kriterium:** Alle Kanten mit `rule_id` ODER `provenance` beginnend mit `"candidate:"` werden dem LLM-Validator vorgelegt.
|
||||
* **Kontext-Optimierung:** Dynamische Kontext-Auswahl basierend auf `scope`:
|
||||
* **Note-Scope (`scope: note`):** Verwendet `note_summary` (Top 5 Chunks) oder `note_text` (aggregierter Gesamttext) für globale Verbindungen.
|
||||
* **Chunk-Scope (`scope: chunk`):** Versucht spezifischen Chunk-Text zu finden, sonst Fallback auf Note-Text.
|
||||
* **Validierung:** Nutzt `validate_edge_candidate()` mit MoE-Profil `ingest_validator` (Temperature 0.0 für Determinismus).
|
||||
* **Erfolg (VERIFIED):** Entfernt `candidate:` Präfix aus `rule_id` und `provenance`. Kante wird zu `validated_edges` hinzugefügt.
|
||||
* **Ablehnung (REJECTED):** Kante wird zu `rejected_edges` hinzugefügt und **nicht** weiterverarbeitet (keine DB-Persistierung).
|
||||
* **Fehlertoleranz:** Unterscheidung zwischen transienten (Netzwerk) und permanenten (Config) Fehlern:
|
||||
* **Transiente Fehler:** Timeout, Connection, Network → Kante wird erlaubt (Integrität vor Präzision)
|
||||
* **Permanente Fehler:** Config, Validation, Invalid Response → Kante wird abgelehnt (Graph-Qualität schützen)
|
||||
* **Logging:** `🚀 [PHASE 3]` für Start, `✅ [PHASE 3] VERIFIED` für Erfolg, `🚫 [PHASE 3] REJECTED` für Ablehnung.
|
||||
|
||||
**Wichtig:** Nur `validated_edges` (ohne `candidate:` Präfix) werden in Phase 2 (Symmetrie) verarbeitet und in die Datenbank geschrieben. `rejected_edges` werden vollständig ignoriert.
|
||||
|
||||
### Phase 2 (Fortsetzung): Symmetrie & Persistence
|
||||
|
||||
18. **Database Sync (WP-14):** Batch-Upsert aller Points in die Collections `{prefix}_chunks` und `{prefix}_edges` über die zentrale Infrastruktur.
|
||||
* **Nur verified Kanten:** Nur Kanten ohne `candidate:` Präfix werden persistiert.
|
||||
15. **Embedding (Async):** Generierung der Vektoren via `nomic-embed-text` (768 Dimensionen).
|
||||
16. **Database Sync (WP-14):** Batch-Upsert aller Points in die Collections `{prefix}_chunks` und `{prefix}_edges` über die zentrale Infrastruktur.
|
||||
|
||||
---
|
||||
|
||||
## 2. Betrieb & CLI Befehle
|
||||
|
||||
### 2.1 API-Endpunkt: `/ingest/save` (Background Tasks)
|
||||
|
||||
Seit WP-14 nutzt der `/ingest/save` Endpunkt **Background Tasks** für non-blocking Ingestion:
|
||||
|
||||
**Workflow:**
|
||||
1. **Request:** Frontend sendet Markdown an `/ingest/save`
|
||||
2. **Sofortige Antwort:** API antwortet mit `status: "queued"` und `note_id: "pending"`
|
||||
3. **Datei-Persistenz:** Markdown wird sofort auf Festplatte geschrieben
|
||||
4. **Background Task:** Ingestion läuft asynchron im Hintergrund
|
||||
- Chunking
|
||||
- Embedding-Generierung
|
||||
- Smart Edge Allocation (WP-15)
|
||||
- Hybrid-Cloud-Analyse (WP-20)
|
||||
5. **Vorteil:** Keine Timeouts bei großen Dateien oder langsamen LLM-Calls
|
||||
|
||||
**Hinweis:** Die tatsächliche `note_id` steht erst nach dem Parsing fest. Das Frontend sollte den `file_path` für Tracking nutzen.
|
||||
|
||||
### 2.2 CLI-Betrieb (Inkrementell)
|
||||
### 2.1 Standard-Betrieb (Inkrementell)
|
||||
Erkennt Änderungen via Multi-Hash.
|
||||
|
||||
```bash
|
||||
|
|
@ -149,44 +109,19 @@ Das Chunking ist profilbasiert und bezieht seine Konfiguration dynamisch aus der
|
|||
| `sliding_smart_edges`| `sliding_window` | Max: 600, Target: 400 | Fließtexte (Projekte). |
|
||||
| `structured_smart_edges` | `by_heading` | `strict: false` | Strukturierte Texte. |
|
||||
|
||||
### 3.2 Die `by_heading` Logik (v3.9.9 Atomic Section Logic)
|
||||
### 3.2 Die `by_heading` Logik (v2.9 Hybrid)
|
||||
|
||||
Die Strategie `by_heading` implementiert seit v3.9.9 das **"Pack-and-Carry-Over"** Verfahren (Regel 1-3), um Sektions-Überschriften und deren Inhalte atomar in Chunks zu halten.
|
||||
Die Strategie `by_heading` zerlegt Texte anhand ihrer Struktur (Überschriften). Sie unterstützt ein "Safety Net" gegen zu große Chunks.
|
||||
|
||||
**Kernprinzipien:**
|
||||
* **Atomic Section Logic:** Überschriften und deren Inhalte werden als atomare Einheiten behandelt und nicht über Chunk-Grenzen hinweg getrennt.
|
||||
* **H1-Context Preservation:** Der Dokumenttitel (H1) wird zuverlässig als Breadcrumb in das Embedding-Fenster (`window`) aller Chunks injiziert.
|
||||
* **Signature Alignment:** Parameter-Synchronisierung zwischen Orchestrator und Strategien (`context_prefix` statt `doc_title`).
|
||||
|
||||
**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.
|
||||
* *Merge-Check:* Wenn der vorherige Chunk leer war (nur Überschriften), wird gemergt.
|
||||
* *Safety Net:* Wird ein Abschnitt zu lang (> `max` Token), wird auch ohne Überschrift getrennt.
|
||||
|
||||
**Modus "Soft" (`strict_heading_split: false`):**
|
||||
* **Hierarchie-Check:** Überschriften *oberhalb* des Split-Levels erzwingen **immer** einen Split.
|
||||
* **Füll-Logik:** Überschriften *auf* dem Split-Level lösen nur dann einen neuen Chunk aus, wenn der aktuelle Chunk die `target`-Größe erreicht hat.
|
||||
* **Pack-and-Carry-Over:** Wenn ein Abschnitt zu groß ist, wird er intelligent zerlegt, wobei der Rest (mit Überschrift) zurück in die Queue gelegt wird.
|
||||
* *Safety Net:* Auch hier greift das `max` Token Limit.
|
||||
|
||||
### 3.3 Registry-First Profiling (v2.13.12)
|
||||
|
||||
Seit v2.13.12 nutzt der `IngestionService` die korrekte Hierarchie zur Ermittlung des Chunking-Profils:
|
||||
|
||||
**Priorität:**
|
||||
1. **Frontmatter** (Override) - Explizite `chunking_profile` Angabe
|
||||
2. **`types.yaml` Typ-Config** - Profil basierend auf `type`
|
||||
3. **Global Defaults** - Fallback auf `sliding_standard`
|
||||
|
||||
**Wichtig:** Ein Hard-Fallback auf `sliding_standard` erfolgt nur noch, wenn keine Konfiguration existiert. Dies stellt sicher, dass Note-Typen wie `value` automatisch das korrekte Profil (z.B. `structured_smart_edges_strict`) erhalten.
|
||||
|
||||
### 3.4 Deterministic Hashing (v2.13.12)
|
||||
|
||||
Der `full`-Hash inkludiert nun alle strategischen Parameter (z.B. `split_level`, `strict_heading_split`), sodass Konfigurationsänderungen im Frontmatter zwingend einen Re-Import auslösen.
|
||||
|
||||
**Impact:** Änderungen an Chunking-Parametern werden zuverlässig erkannt, auch wenn der Text unverändert bleibt.
|
||||
* **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.
|
||||
* *Merge-Check:* Wenn der vorherige Chunk leer war (nur Überschriften), wird gemergt.
|
||||
* *Safety Net:* Wird ein Abschnitt zu lang (> `max` Token), wird auch ohne Überschrift getrennt.
|
||||
* **Modus "Soft" (`strict_heading_split: false`):**
|
||||
* **Hierarchie-Check:** Überschriften *oberhalb* des Split-Levels erzwingen **immer** einen Split.
|
||||
* **Füll-Logik:** Überschriften *auf* dem Split-Level lösen nur dann einen neuen Chunk aus, wenn der aktuelle Chunk die `target`-Größe erreicht hat.
|
||||
* *Safety Net:* Auch hier greift das `max` Token Limit.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -211,8 +146,4 @@ Kanten werden nach Vertrauenswürdigkeit (`provenance`) priorisiert. Die höhere
|
|||
|
||||
**2. Mistral-safe Parsing:** Automatisierte Bereinigung von LLM-Antworten in `ingestion_validation.py`. Stellt sicher, dass semantische Entscheidungen ("YES"/"NO") nicht durch technische Header verfälscht werden.
|
||||
|
||||
**3. Phase 3 Agentic Edge Validation (WP-24c v4.5.8):** Finales Validierungs-Gate für alle `candidate:` Kanten. Nutzt das MoE-Profil `ingest_validator` (Temperature 0.0 für Determinismus) und dynamische Kontext-Optimierung (Note-Scope vs. Chunk-Scope). Gewährleistet konsistente binäre Entscheidungen (YES/NO) und verhindert "Geister-Verknüpfungen" im Wissensgraphen.
|
||||
|
||||
**4. Profilgesteuerte Validierung (WP-25a):** Die semantische Kanten-Validierung erfolgt zwingend über das MoE-Profil `ingest_validator` (Temperature 0.0 für Determinismus). Dies gewährleistet konsistente binäre Entscheidungen (YES/NO) unabhängig von der globalen Provider-Konfiguration.
|
||||
|
||||
**3. Purge Integrity:** Validierung, dass vor jedem Upsert alle assoziierten Artefakte in den Collections `{prefix}_chunks` und `{prefix}_edges` gelöscht wurden, um Daten-Duplikate zu vermeiden.
|
||||
|
|
@ -3,29 +3,22 @@ doc_type: technical_reference
|
|||
audience: developer, data_scientist
|
||||
scope: backend, retrieval, scoring, modularization
|
||||
status: active
|
||||
version: 2.9.1
|
||||
context: "Detaillierte Dokumentation der Scoring-Algorithmen, inklusive WP-22 Lifecycle-Modifier, Intent-Boosting, WP-15c Diversity Engine und WP-14 Modularisierung."
|
||||
version: 2.9.0
|
||||
context: "Detaillierte Dokumentation der Scoring-Algorithmen, inklusive WP-22 Lifecycle-Modifier, Intent-Boosting und WP-14 Modularisierung."
|
||||
---
|
||||
|
||||
# Retrieval & Scoring Algorithmen
|
||||
|
||||
Der Retriever unterstützt **Semantic Search** und **Hybrid Search**. Seit v2.4 nutzt Mindnet ein gewichtetes Scoring-Modell, das Semantik, Graphentheorie und Metadaten kombiniert. Mit Version 2.7 (WP-22) wurde dieses Modell um **Lifecycle-Faktoren** und **Intent-Boosting** erweitert sowie die Architektur modularisiert (WP-14).
|
||||
|
||||
## 1. Scoring Formel (WP-15c / v1.0.3)
|
||||
## 1. Scoring Formel (v2.7.0)
|
||||
|
||||
Seit WP-15c nutzt Mindnet eine **hybride Multiplikations-Formel** für präziseres Scoring. Alle Gewichte ($W$) und Modifier ($M$) sind in `retriever.yaml` und `decision_engine.yaml` konfigurierbar.
|
||||
Der Gesamtscore eines Treffers berechnet sich als gewichtete Summe. Alle Gewichte ($W$) und Modifier ($M$) sind in `retriever.yaml` und `decision_engine.yaml` konfigurierbar.
|
||||
|
||||
$$
|
||||
Final = (Semantic \cdot StatusMult) \cdot (1 + TypeBoost + EdgeBonus + CentBonus)
|
||||
TotalScore = (W_{sem} \cdot S_{sem} \cdot W_{type} \cdot M_{status}) + (W_{edge} \cdot B_{edge}) + (W_{cent} \cdot B_{cent}) + B_{intent}
|
||||
$$
|
||||
|
||||
**Komponenten:**
|
||||
* **Base Score:** `Semantic * StatusMult` (Lifecycle-Filter)
|
||||
* **Boosts:** `TypeBoost + EdgeBonus + CentBonus` (additiv, dann multiplikativ auf Base)
|
||||
* **Graph Boost Factor:** Intent-spezifische Verstärkung (1.5x bei aktivem Intent)
|
||||
|
||||
**Vorteil:** Status wirkt als Multiplikator auf die Basis-Relevanz, Graph-Boni werden proportional verstärkt.
|
||||
|
||||
### Die Komponenten (Klassisch v2.4)
|
||||
|
||||
**1. Semantic Score ($S_{sem}$):**
|
||||
|
|
@ -50,15 +43,13 @@ $$
|
|||
|
||||
### Die WP-22 Erweiterungen (v2.7.0)
|
||||
|
||||
**5. Status Modifier ($M_{status}$) - Status-Gatekeeper:**
|
||||
**5. Status Modifier ($M_{status}$):**
|
||||
* **Herkunft:** Feld `status` aus dem Frontmatter (verarbeitet in `retriever_scoring.get_status_multiplier`).
|
||||
* **Zweck:** Wirkt als **Multiplikator** auf die Basis-Semantik. Bestraft unfertiges Wissen (Drafts) oder bevorzugt stabiles Wissen.
|
||||
* **Werte (WP-15c):**
|
||||
* `stable`: **1.2** (Belohnung für verifiziertes Wissen)
|
||||
* `active`: **1.0** (Standard-Gewichtung)
|
||||
* `draft`: **0.5** (Malus für unfertige Fragmente)
|
||||
* `system`: Exkludiert (siehe Ingestion Lifecycle Filter)
|
||||
* **Impact:** Der Status wirkt direkt auf die semantische Ähnlichkeit, bevor Graph-Boni berechnet werden.
|
||||
* **Zweck:** Bestraft unfertiges Wissen (Drafts) oder bevorzugt stabiles Wissen.
|
||||
* **Werte (Auftrag WP-22):** * `stable`: **1.2** (Belohnung für verifiziertes Wissen).
|
||||
* `active`: **1.0** (Standard-Gewichtung).
|
||||
* `draft`: **0.5** (Malus für unfertige Fragmente).
|
||||
* `system`: Exkludiert (siehe Ingestion Lifecycle Filter).
|
||||
|
||||
**6. Intent Boost ($B_{intent}$):**
|
||||
* **Herkunft:** Dynamische Injektion durch die Decision Engine basierend auf der Nutzerfrage.
|
||||
|
|
@ -79,25 +70,16 @@ Seit v2.9 ist die Retrieval-Engine im spezialisierten Paket `app.core.retrieval`
|
|||
* Diese delegiert an `app.core.graph.graph_subgraph`, um direkte Nachbarn aus der `_edges` Collection zu laden.
|
||||
* Konstruktion eines in-memory Graphen zur Berechnung topologischer Boni.
|
||||
|
||||
**Phase 3: Graph-Intelligenz & Super-Edge Aggregation (WP-15c)**
|
||||
* **Super-Edge Aggregation:** Parallele Kanten zwischen zwei Notizen (z.B. auf verschiedene Sections) werden mathematisch zu einer "Super-Edge" aggregiert:
|
||||
* Primäre Kante zählt voll (höchstes Gewicht)
|
||||
* Weitere Kanten werden mit Dämpfungsfaktor **0.1** gewichtet
|
||||
* Verhindert Score-Explosionen durch multiple Links
|
||||
* **Provenance Weighting:** Kanten werden nach Provenance gewichtet (`explicit`=1.0, `smart`=0.9, `rule`=0.7)
|
||||
* **Intent Boost:** Dynamische Multiplikatoren für spezifische Kanten-Typen (z.B. `caused_by` bei "Warum"-Fragen)
|
||||
|
||||
**Phase 4: Re-Ranking & Diversity Pooling (WP-15c)**
|
||||
**Phase 3: Re-Ranking (Modular)**
|
||||
* Der Orchestrator übergibt den Graphen und die Seeds an die `ScoringEngine` (`retriever_scoring.py`).
|
||||
* Berechnung der finalen Scores unter Berücksichtigung von $B_{edge}$, $B_{cent}$ sowie der Lifecycle- und Intent-Modifier.
|
||||
* **Note-Level Diversity Pooling:** Pro `note_id` wird nur der relevanteste Treffer behalten (verhindert "Note-Flooding").
|
||||
* Sortierung absteigend nach `TotalScore` und Limitierung auf die angeforderten Top-Resultate.
|
||||
|
||||
---
|
||||
|
||||
## 3. Explanation Layer (WP-15c)
|
||||
## 3. Explanation Layer (WP-22 Update)
|
||||
|
||||
Bei `explain=True` generiert das System eine detaillierte Begründung inklusive Provenienz-Informationen. Der Explanation Layer liefert detaillierte Begründungen für jeden Bonus (z.B. Sektions-Links oder Hub-Zentralität), was die Transparenz massiv erhöht.
|
||||
Bei `explain=True` generiert das System eine detaillierte Begründung inklusive Provenienz-Informationen.
|
||||
|
||||
**Erweiterte JSON-Struktur:**
|
||||
|
||||
|
|
|
|||
|
|
@ -1,265 +0,0 @@
|
|||
# Audit: Informations-Integrität (Clean-Context v4.2.0)
|
||||
|
||||
**Datum:** 2026-01-10
|
||||
**Version:** v4.2.0
|
||||
**Status:** Audit abgeschlossen - **KRITISCHES PROBLEM IDENTIFIZIERT**
|
||||
|
||||
## Kontext
|
||||
|
||||
Das System wurde auf den Gold-Standard v4.2.0 optimiert. Ziel ist der "Clean-Context"-Ansatz: Strukturelle Metadaten (speziell `> [!edge]` Callouts und definierte Note-Scope Zonen) werden aus den Text-Chunks entfernt, um das semantische Rauschen im Vektor-Index zu reduzieren. Diese Informationen müssen stattdessen exklusiv über den Graphen (Feld `explanation` im `QueryHit`) an das LLM geliefert werden.
|
||||
|
||||
## Audit-Ergebnisse
|
||||
|
||||
### 1. Extraktion vor Filterung (Temporal Integrity) ⚠️ **TEILWEISE**
|
||||
|
||||
#### ✅ Note-Scope Zonen: **FUNKTIONIERT**
|
||||
|
||||
**Status:** ✅ **KORREKT**
|
||||
|
||||
- `build_edges_for_note()` erhält `markdown_body` (Original-Markdown) als Parameter
|
||||
- `extract_note_scope_zones()` analysiert den **unbearbeiteten** Markdown-Text
|
||||
- Extraktion erfolgt **VOR** dem Chunking-Filter
|
||||
- **Code-Referenz:** `app/core/graph/graph_derive_edges.py` Zeile 152-177
|
||||
|
||||
```python
|
||||
# WP-24c v4.2.0: Note-Scope Zonen Extraktion (VOR Chunk-Verarbeitung)
|
||||
note_scope_edges: List[dict] = []
|
||||
if markdown_body:
|
||||
zone_links = extract_note_scope_zones(markdown_body) # ← Original-Markdown
|
||||
```
|
||||
|
||||
#### ❌ Callouts in Edge-Zonen: **KRITISCHES PROBLEM**
|
||||
|
||||
**Status:** ❌ **FEHLT**
|
||||
|
||||
**Problem:**
|
||||
- `build_edges_for_note()` extrahiert Callouts aus **gefilterten Chunks** (Zeile 217-265)
|
||||
- Chunks wurden bereits gefiltert (Edge-Zonen entfernt) in `chunking_processor.py` Zeile 38
|
||||
- **Callouts in Edge-Zonen werden NICHT extrahiert!**
|
||||
|
||||
**Code-Referenz:**
|
||||
```python
|
||||
# app/core/graph/graph_derive_edges.py Zeile 217-265
|
||||
for ch in chunks: # ← chunks sind bereits gefiltert!
|
||||
raw = _get(ch, "window") or _get(ch, "text") or ""
|
||||
# ...
|
||||
# C. Callouts (> [!edge])
|
||||
call_pairs, rem2 = extract_callout_relations(rem) # ← rem kommt aus gefilterten chunks
|
||||
```
|
||||
|
||||
**Konsequenz:**
|
||||
- Callouts in Edge-Zonen (z.B. `### Unzugeordnete Kanten` oder `## Smart Edges`) werden **nicht** in den Graph geschrieben
|
||||
- **Informationsverlust:** Diese Kanten existieren nicht im Graph und können nicht über `explanation` an das LLM geliefert werden
|
||||
|
||||
**Empfehlung:**
|
||||
- Callouts müssen **auch** aus dem Original-Markdown (`markdown_body`) extrahiert werden
|
||||
- Ähnlich wie `extract_note_scope_zones()` sollte eine Funktion `extract_callouts_from_markdown()` erstellt werden
|
||||
- Diese sollte **vor** der Chunk-Verarbeitung aufgerufen werden
|
||||
|
||||
### 2. Payload-Vollständigkeit (Explanation-Mapping) ✅ **FUNKTIONIERT**
|
||||
|
||||
**Status:** ✅ **KORREKT** (wenn Edges im Graph sind)
|
||||
|
||||
**Code-Referenz:** `app/core/retrieval/retriever.py` Zeile 188-238
|
||||
|
||||
**Verifizierung:**
|
||||
- ✅ `_build_explanation()` sammelt alle Edges aus dem Subgraph (Zeile 189-215)
|
||||
- ✅ Edges werden in `EdgeDTO`-Objekte konvertiert (Zeile 205-214)
|
||||
- ✅ `related_edges` werden im `Explanation`-Objekt gespeichert (Zeile 236)
|
||||
- ✅ Top 3 Edges werden als `Reason`-Objekte formuliert (Zeile 217-228)
|
||||
|
||||
**Einschränkung:**
|
||||
- Funktioniert nur, wenn Edges **im Graph sind**
|
||||
- Da Callouts in Edge-Zonen nicht extrahiert werden (siehe Punkt 1), fehlen sie auch in der Explanation
|
||||
|
||||
### 3. Prompt-Sichtbarkeit (RAG-Interface) ⚠️ **UNKLAR**
|
||||
|
||||
**Status:** ⚠️ **TEILWEISE DOKUMENTIERT**
|
||||
|
||||
**Code-Referenz:** `app/routers/chat.py` Zeile 178-274
|
||||
|
||||
**Verifizierung:**
|
||||
- ✅ `explain=True` wird in `QueryRequest` gesetzt (Zeile 211 in `decision_engine.py`)
|
||||
- ✅ `explanation` wird im `QueryHit` gespeichert (Zeile 334 in `retriever.py`)
|
||||
- ⚠️ **Unklar:** Wie wird `explanation.related_edges` im LLM-Prompt verwendet?
|
||||
|
||||
**Untersuchung:**
|
||||
- `chat.py` verwendet `interview_template` Prompt (Zeile 212-222)
|
||||
- Prompt-Variablen werden aus `QueryHit` extrahiert
|
||||
- **Fehlend:** Explizite Verwendung von `explanation.related_edges` im Prompt
|
||||
|
||||
**Empfehlung:**
|
||||
- Prüfen Sie `config/prompts.yaml` für `interview_template`
|
||||
- Stellen Sie sicher, dass `{related_edges}` oder ähnliche Variablen im Prompt verwendet werden
|
||||
- Dokumentieren Sie die Prompt-Struktur für RAG-Kontext
|
||||
|
||||
### 4. Edge-Case Analyse ⚠️ **KRITISCH**
|
||||
|
||||
#### Szenario: Callout nur in Edge-Zone (kein Wikilink im Fließtext)
|
||||
|
||||
**Status:** ❌ **INFORMATIONSVERLUST**
|
||||
|
||||
**Beispiel:**
|
||||
```markdown
|
||||
---
|
||||
type: decision
|
||||
title: Meine Notiz
|
||||
---
|
||||
|
||||
# Hauptinhalt
|
||||
|
||||
Dieser Text wird gechunkt.
|
||||
|
||||
## Smart Edges
|
||||
|
||||
> [!edge] depends_on
|
||||
> [[Projekt Alpha]]
|
||||
|
||||
## Weiterer Inhalt
|
||||
|
||||
Mehr Text...
|
||||
```
|
||||
|
||||
**Aktuelles Verhalten:**
|
||||
1. ✅ `## Smart Edges` wird als Edge-Zone erkannt
|
||||
2. ✅ Zone wird vom Chunking ausgeschlossen
|
||||
3. ❌ **Callout wird NICHT extrahiert** (weil aus gefilterten Chunks extrahiert wird)
|
||||
4. ❌ **Kante fehlt im Graph**
|
||||
5. ❌ **Kante fehlt in Explanation**
|
||||
6. ❌ **LLM erhält keine Information über diese Verbindung**
|
||||
|
||||
**Konsequenz:**
|
||||
- **Wissens-Vakuum:** Die Information existiert weder im Chunk-Text noch im Graph
|
||||
- **Semantische Verbindung verloren:** Das LLM kann diese Verbindung nicht berücksichtigen
|
||||
|
||||
## Zusammenfassung der Probleme
|
||||
|
||||
### ❌ **KRITISCH: Callout-Extraktion aus Edge-Zonen fehlt**
|
||||
|
||||
**Problem:**
|
||||
- Callouts werden nur aus gefilterten Chunks extrahiert
|
||||
- Callouts in Edge-Zonen werden nicht erfasst
|
||||
- **Informationsverlust:** Diese Kanten fehlen im Graph
|
||||
|
||||
**Lösung:**
|
||||
1. Erstellen Sie `extract_callouts_from_markdown(markdown_body: str)` Funktion
|
||||
2. Rufen Sie diese **vor** der Chunk-Verarbeitung auf
|
||||
3. Integrieren Sie die extrahierten Callouts in `build_edges_for_note()`
|
||||
|
||||
### ⚠️ **WARNUNG: Prompt-Integration unklar**
|
||||
|
||||
**Problem:**
|
||||
- Unklar, ob `explanation.related_edges` im LLM-Prompt verwendet werden
|
||||
- Keine explizite Dokumentation der Prompt-Struktur
|
||||
|
||||
**Empfehlung:**
|
||||
- Prüfen Sie `config/prompts.yaml` für `interview_template`
|
||||
- Dokumentieren Sie die Verwendung von `related_edges` im Prompt
|
||||
|
||||
## Empfohlene Fixes
|
||||
|
||||
### Fix 1: Callout-Extraktion aus Original-Markdown
|
||||
|
||||
**Datei:** `app/core/graph/graph_derive_edges.py`
|
||||
|
||||
**Änderung:**
|
||||
```python
|
||||
def extract_callouts_from_markdown(markdown_body: str, note_id: str) -> List[dict]:
|
||||
"""
|
||||
WP-24c v4.2.0: Extrahiert Callouts aus dem Original-Markdown.
|
||||
Wird verwendet, um Callouts in Edge-Zonen zu erfassen, die nicht in Chunks sind.
|
||||
"""
|
||||
if not markdown_body:
|
||||
return []
|
||||
|
||||
edges: List[dict] = []
|
||||
|
||||
# Extrahiere alle Callouts aus dem gesamten Markdown
|
||||
call_pairs, _ = extract_callout_relations(markdown_body)
|
||||
|
||||
for k, raw_t in call_pairs:
|
||||
t, sec = parse_link_target(raw_t, note_id)
|
||||
if not t:
|
||||
continue
|
||||
|
||||
# Bestimme scope: "note" wenn in Note-Scope Zone, sonst "chunk"
|
||||
# (Für jetzt: scope="note" für alle Callouts aus Markdown)
|
||||
payload = {
|
||||
"edge_id": _mk_edge_id(k, note_id, t, "note", target_section=sec),
|
||||
"provenance": "explicit:callout",
|
||||
"rule_id": "callout:edge",
|
||||
"confidence": PROVENANCE_PRIORITY.get("callout:edge", 1.0)
|
||||
}
|
||||
if sec:
|
||||
payload["target_section"] = sec
|
||||
|
||||
edges.append(_edge(
|
||||
kind=k,
|
||||
scope="note",
|
||||
source_id=note_id,
|
||||
target_id=t,
|
||||
note_id=note_id,
|
||||
payload=payload
|
||||
))
|
||||
|
||||
return edges
|
||||
|
||||
def build_edges_for_note(
|
||||
note_id: str,
|
||||
chunks: List[dict],
|
||||
note_level_references: Optional[List[str]] = None,
|
||||
include_note_scope_refs: bool = False,
|
||||
markdown_body: Optional[str] = None,
|
||||
) -> List[dict]:
|
||||
# ... existing code ...
|
||||
|
||||
# WP-24c v4.2.0: Callout-Extraktion aus Original-Markdown (VOR Chunk-Verarbeitung)
|
||||
if markdown_body:
|
||||
callout_edges = extract_callouts_from_markdown(markdown_body, note_id)
|
||||
edges.extend(callout_edges)
|
||||
|
||||
# ... rest of function ...
|
||||
```
|
||||
|
||||
### Fix 2: Prompt-Dokumentation
|
||||
|
||||
**Datei:** `config/prompts.yaml` und Dokumentation
|
||||
|
||||
**Empfehlung:**
|
||||
- Prüfen Sie, ob `interview_template` `{related_edges}` verwendet
|
||||
- Falls nicht: Erweitern Sie den Prompt um Graph-Kontext
|
||||
- Dokumentieren Sie die Prompt-Struktur
|
||||
|
||||
## Validierung nach Fix
|
||||
|
||||
Nach Implementierung der Fixes sollte folgendes verifiziert werden:
|
||||
|
||||
1. ✅ **Callouts in Edge-Zonen werden extrahiert**
|
||||
- Test: Erstellen Sie eine Notiz mit Callout in `## Smart Edges`
|
||||
- Verifizieren: Edge existiert in Qdrant `_edges` Collection
|
||||
|
||||
2. ✅ **Edges erscheinen in Explanation**
|
||||
- Test: Query mit `explain=True`
|
||||
- Verifizieren: `explanation.related_edges` enthält die Callout-Edge
|
||||
|
||||
3. ✅ **LLM erhält Graph-Kontext**
|
||||
- Test: Chat-Query mit Edge-Information
|
||||
- Verifizieren: LLM-Antwort berücksichtigt die Graph-Verbindung
|
||||
|
||||
## Fazit
|
||||
|
||||
**Aktueller Status:** ⚠️ **INFORMATIONSVERLUST BEI CALLOUTS IN EDGE-ZONEN**
|
||||
|
||||
**Hauptproblem:**
|
||||
- Callouts in Edge-Zonen werden nicht extrahiert
|
||||
- Diese Information geht vollständig verloren
|
||||
|
||||
**Lösung:**
|
||||
- Implementierung von `extract_callouts_from_markdown()` erforderlich
|
||||
- Integration in `build_edges_for_note()` vor Chunk-Verarbeitung
|
||||
|
||||
**Nach Fix:**
|
||||
- ✅ Alle Callouts werden erfasst (auch in Edge-Zonen)
|
||||
- ✅ Graph-Vollständigkeit gewährleistet
|
||||
- ✅ Explanation enthält alle relevanten Edges
|
||||
- ✅ LLM erhält vollständigen Kontext
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
# Audit: Retriever & Scoring (Gold-Standard v4.1.0)
|
||||
|
||||
**Datum:** 2026-01-10
|
||||
**Version:** v4.1.0
|
||||
**Status:** Audit abgeschlossen, Optimierungen implementiert
|
||||
|
||||
## Kontext
|
||||
|
||||
Das Ingestion-System wurde auf den Gold-Standard v4.1.0 aktualisiert. Die Kanten-Identität ist nun deterministisch und hochpräzise mit strikter Trennung zwischen:
|
||||
|
||||
- **Chunk-Scope-Edges:** Präzise Links aus Textabsätzen (Source = `chunk_id`), oft mit `target_section`
|
||||
- **Note-Scope-Edges:** Strukturelle Links und Symmetrien (Source = `note_id`)
|
||||
- **Multigraph-Support:** Identische Note-Verbindungen bleiben als separate Points erhalten, wenn sie auf unterschiedliche Sektionen zeigen oder aus unterschiedlichen Chunks stammen
|
||||
|
||||
## Prüffragen & Ergebnisse
|
||||
|
||||
### 1. Scope-Awareness ❌ **KRITISCH**
|
||||
|
||||
**Frage:** Sucht der Retriever bei einer Note-Anfrage sowohl nach Abgangskanten der `note_id` als auch nach Abgangskanten aller zugehörigen `chunk_ids`?
|
||||
|
||||
**Aktueller Status:**
|
||||
- ❌ **NEIN**: Der Retriever sucht nur nach Edges, die von `note_id` ausgehen
|
||||
- Die Graph-Expansion in `graph_db_adapter.py` filtert nur nach `source_id`, `target_id` und `note_id`
|
||||
- Chunk-Level Edges (`scope="chunk"`) werden nicht explizit berücksichtigt
|
||||
- **Risiko:** Datenverlust bei präzisen Chunk-Links
|
||||
|
||||
**Empfehlung:**
|
||||
- Erweitere `fetch_edges_from_qdrant` um explizite Suche nach `chunk_id`-Edges
|
||||
- Bei Note-Anfragen: Lade alle Chunks der Note und suche nach deren Edges
|
||||
- Aggregiere Chunk-Edges in Note-Level Scoring
|
||||
|
||||
### 2. Section-Filtering ❌ **FEHLT**
|
||||
|
||||
**Frage:** Kann der Retriever bei einem Sektions-Link (`[[Note#Sektion]]`) die Ergebnismenge in Qdrant gezielt auf Chunks filtern, die das entsprechende `section`-Attribut im Payload tragen?
|
||||
|
||||
**Aktueller Status:**
|
||||
- ❌ **NEIN**: Es gibt keine Filterung nach `target_section`
|
||||
- `target_section` wird zwar im Edge-Payload gespeichert, aber nicht für Filterung verwendet
|
||||
- **Risiko:** Unpräzise Ergebnisse bei Section-Links
|
||||
|
||||
**Empfehlung:**
|
||||
- Erweitere `QueryRequest` um optionales `target_section` Feld
|
||||
- Implementiere Filterung in `_semantic_hits` und `fetch_edges_from_qdrant`
|
||||
- Nutze `target_section` für präzise Chunk-Filterung
|
||||
|
||||
### 3. Scoring-Aggregation ⚠️ **TEILWEISE**
|
||||
|
||||
**Frage:** Wie geht das Scoring damit um, wenn ein Ziel von mehreren Chunks derselben Note referenziert wird? Wird die Relevanz (In-Degree) auf Chunk-Ebene korrekt akkumuliert?
|
||||
|
||||
**Aktueller Status:**
|
||||
- ⚠️ **TEILWEISE**: Super-Edge-Aggregation existiert (WP-15c), aber:
|
||||
- Aggregiert nur nach Ziel-Note (`target_id`), nicht nach Chunk-Level
|
||||
- Mehrere Chunks derselben Note, die auf dasselbe Ziel zeigen, werden nicht korrekt akkumuliert
|
||||
- Die "Beweislast" (In-Degree) wird nicht auf Chunk-Ebene berechnet
|
||||
- **Risiko:** Unterbewertung von Zielen, die von mehreren Chunks referenziert werden
|
||||
|
||||
**Empfehlung:**
|
||||
- Erweitere Super-Edge-Aggregation um Chunk-Level Tracking
|
||||
- Berechne In-Degree sowohl auf Note- als auch auf Chunk-Ebene
|
||||
- Nutze Chunk-Level In-Degree als zusätzlichen Boost-Faktor
|
||||
|
||||
### 4. Authority-Priorisierung ⚠️ **TEILWEISE**
|
||||
|
||||
**Frage:** Nutzt das Scoring das Feld `provenance_priority` oder `confidence`, um manuelle "Explicit"-Kanten gegenüber "Virtual"-Symmetrien bei der Sortierung zu bevorzugen?
|
||||
|
||||
**Aktueller Status:**
|
||||
- ⚠️ **TEILWEISE**:
|
||||
- Provenance-Weighting existiert (Zeile 344-345 in `retriever.py`)
|
||||
- Nutzt aber nicht `confidence` oder `provenance_priority` aus dem Payload
|
||||
- Hardcoded Gewichtung: `explicit=1.0`, `smart=0.9`, `rule=0.7`
|
||||
- `virtual` Flag wird nicht berücksichtigt
|
||||
- **Risiko:** Virtual-Symmetrien werden nicht korrekt de-priorisiert
|
||||
|
||||
**Empfehlung:**
|
||||
- Nutze `confidence` aus dem Edge-Payload
|
||||
- Berücksichtige `virtual` Flag für explizite De-Priorisierung
|
||||
- Integriere `PROVENANCE_PRIORITY` aus `graph_utils.py` statt Hardcoding
|
||||
|
||||
### 5. RAG-Kontext ❌ **FEHLT**
|
||||
|
||||
**Frage:** Wird beim Retrieval einer Kante der `source_id` (Chunk) direkt mitgeliefert, damit das LLM den exakten Herkunfts-Kontext der Verbindung erhält?
|
||||
|
||||
**Aktueller Status:**
|
||||
- ❌ **NEIN**: `source_id` (Chunk-ID) wird nicht explizit im `QueryHit` mitgeliefert
|
||||
- Edge-Payload enthält `source_id`, aber es wird nicht in den RAG-Kontext übernommen
|
||||
- **Risiko:** LLM erhält keinen Kontext über die Herkunft der Verbindung
|
||||
|
||||
**Empfehlung:**
|
||||
- Erweitere `QueryHit` um `source_chunk_id` Feld
|
||||
- Bei Chunk-Scope Edges: Lade den Quell-Chunk-Text für RAG-Kontext
|
||||
- Integriere Chunk-Kontext in Explanation Layer
|
||||
|
||||
## Implementierte Optimierungen
|
||||
|
||||
Siehe: `app/core/retrieval/retriever.py` (v0.8.0) und `app/core/graph/graph_db_adapter.py` (v1.2.0)
|
||||
|
||||
### Änderungen
|
||||
|
||||
1. **Scope-Aware Edge Retrieval**
|
||||
- `fetch_edges_from_qdrant` sucht nun explizit nach `chunk_id`-Edges
|
||||
- Bei Note-Anfragen werden alle zugehörigen Chunks geladen
|
||||
|
||||
2. **Section-Filtering**
|
||||
- `QueryRequest` unterstützt optionales `target_section` Feld
|
||||
- Filterung in `_semantic_hits` und Edge-Retrieval implementiert
|
||||
|
||||
3. **Chunk-Level Aggregation**
|
||||
- Super-Edge-Aggregation erweitert um Chunk-Level Tracking
|
||||
- In-Degree wird sowohl auf Note- als auch Chunk-Ebene berechnet
|
||||
|
||||
4. **Authority-Priorisierung**
|
||||
- Nutzung von `confidence` und `PROVENANCE_PRIORITY`
|
||||
- `virtual` Flag wird für De-Priorisierung berücksichtigt
|
||||
|
||||
5. **RAG-Kontext**
|
||||
- `QueryHit` erweitert um `source_chunk_id`
|
||||
- Chunk-Kontext wird in Explanation Layer integriert
|
||||
|
||||
## Validierung
|
||||
|
||||
- ✅ Scope-Awareness: Note- und Chunk-Edges werden korrekt geladen
|
||||
- ✅ Section-Filtering: Präzise Filterung nach `target_section` funktioniert
|
||||
- ✅ Scoring-Aggregation: Chunk-Level In-Degree wird korrekt akkumuliert
|
||||
- ✅ Authority-Priorisierung: Explicit-Kanten werden bevorzugt
|
||||
- ✅ RAG-Kontext: `source_chunk_id` wird mitgeliefert
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
1. Performance-Tests mit großen Vaults
|
||||
2. Integration in Decision Engine
|
||||
3. Dokumentation der neuen Features
|
||||
|
|
@ -1,510 +0,0 @@
|
|||
# System-Integrity & Regression-Audit (v4.5.8)
|
||||
|
||||
**Datum:** 2026-01-XX
|
||||
**Version:** v4.5.8
|
||||
**Status:** Audit abgeschlossen
|
||||
**Auditor:** AI Assistant (Auto)
|
||||
|
||||
## Kontext
|
||||
|
||||
Nach umfangreichen Änderungen in WP24c (insbesondere v4.5.7/8) wurde ein vollständiges System-Integrity & Regression-Audit durchgeführt, um sicherzustellen, dass keine unbeabsichtigten Beeinträchtigungen oder "Logic-Drift" eingeführt wurden.
|
||||
|
||||
## Audit-Scope
|
||||
|
||||
1. **WP-22 Scoring Integrität**: Prüfung der mathematischen Berechnung des `total_score`
|
||||
2. **WP-25a/b MoE & Prompts**: Verifizierung der Profil-Ladung und MoE-Kaskade
|
||||
3. **Deduplizierungs-Logik**: Prüfung der De-Duplizierung von Kanten
|
||||
4. **Phase 3 Validierungs-Gate**: Verifizierung der neuen Validierungs-Logik
|
||||
5. **Note-Scope Kontext-Optimierung**: Prüfung der Kontext-Optimierung
|
||||
|
||||
---
|
||||
|
||||
## 1. WP-22 Scoring Integrität
|
||||
|
||||
### Prüfpunkt: Hat die Einführung von `candidate:` oder `verified` Status Auswirkungen auf die mathematische Berechnung des `total_score`?
|
||||
|
||||
**Status:** ✅ **KEIN PROBLEM**
|
||||
|
||||
**Ergebnis:**
|
||||
- `candidate:` und `verified` sind **KEINE Status-Werte** für die Scoring-Funktion
|
||||
- Sie sind **Präfixe** in `rule_id` und `provenance` für Kanten (Edge-Metadaten)
|
||||
- Die `get_status_multiplier()` Funktion in `retriever_scoring.py` behandelt ausschließlich:
|
||||
- `stable`: 1.2 (Multiplikator)
|
||||
- `active`: 1.0 (Standard)
|
||||
- `draft`: 0.5 (Dämpfung)
|
||||
- Die mathematische Formel in `compute_wp22_score()` bleibt vollständig unangetastet
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/core/retrieval/retriever_scoring.py` Zeile 49-63: `get_status_multiplier()`
|
||||
- `app/core/retrieval/retriever_scoring.py` Zeile 65-128: `compute_wp22_score()`
|
||||
|
||||
**Bewertung:** Die Scoring-Mathematik ist **vollständig isoliert** von den Edge-Metadaten (`candidate:`, `verified`). Keine Regression festgestellt.
|
||||
|
||||
---
|
||||
|
||||
## 2. WP-25a/b MoE & Prompts
|
||||
|
||||
### Prüfpunkt 2a: Werden die korrekten Profile aus `llm_profiles.yaml` geladen?
|
||||
|
||||
**Status:** ✅ **FUNKTIONIERT KORREKT**
|
||||
|
||||
**Ergebnis:**
|
||||
- `LLMService._load_llm_profiles()` lädt Profile aus `llm_profiles.yaml` (nicht `prompts.yaml`)
|
||||
- Pfad wird korrekt aus Settings geladen: `LLM_PROFILES_PATH` (Default: `config/llm_profiles.yaml`)
|
||||
- Profile werden im `__init__` geladen und im Instanz-Attribut `self.profiles` gespeichert
|
||||
- Fehlerbehandlung vorhanden: Bei fehlender Datei wird leeres Dict zurückgegeben mit Warnung
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/services/llm_service.py` Zeile 87-100: `_load_llm_profiles()`
|
||||
- `app/services/llm_service.py` Zeile 36: Initialisierung in `__init__`
|
||||
|
||||
**Bewertung:** Profil-Ladung funktioniert korrekt. Keine Regression.
|
||||
|
||||
### Prüfpunkt 2b: Nutzt die neue Validierungs-Logik in Phase 3 die bestehende MoE-Kaskade?
|
||||
|
||||
**Status:** ✅ **FUNKTIONIERT KORREKT**
|
||||
|
||||
**Ergebnis:**
|
||||
- Phase 3 Validierung nutzt `profile_name="ingest_validator"` (siehe `ingestion_processor.py` Zeile 345)
|
||||
- `LLMService.generate_raw_response()` unterstützt vollständig die MoE-Kaskade:
|
||||
- Profil-Auflösung aus `llm_profiles.yaml` (Zeile 151-161)
|
||||
- Fallback-Kaskade via `fallback_profile` (Zeile 214-227)
|
||||
- `visited_profiles` Schutz verhindert Endlosschleifen (Zeile 214)
|
||||
- Rekursiver Aufruf mit `visited_profiles` Parameter (Zeile 226)
|
||||
- Die Kaskade wird **nicht umgangen**, sondern vollständig genutzt
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/core/ingestion/ingestion_processor.py` Zeile 340-346: Phase 3 Validierung
|
||||
- `app/services/llm_service.py` Zeile 150-227: MoE-Kaskade Implementierung
|
||||
- `config/llm_profiles.yaml`: Profil-Definitionen mit `fallback_profile`
|
||||
|
||||
**Bewertung:** MoE-Kaskade wird korrekt genutzt. Keine Regression.
|
||||
|
||||
### Prüfpunkt 2c: Werden Prompts korrekt aus `prompts.yaml` geladen?
|
||||
|
||||
**Status:** ✅ **FUNKTIONIERT KORREKT**
|
||||
|
||||
**Ergebnis:**
|
||||
- `LLMService._load_prompts()` lädt Prompts aus `prompts.yaml` (Zeile 76-85)
|
||||
- `DecisionEngine` nutzt `prompt_key` und `variables` für Lazy-Loading (Zeile 108-113, 309-315)
|
||||
- `LLMService.get_prompt()` unterstützt Hierarchie: Model-ID → Provider → Default (Zeile 102-123)
|
||||
- Prompt-Formatierung erfolgt via `template.format(**(variables or {}))` (Zeile 179)
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/services/llm_service.py` Zeile 76-85: `_load_prompts()`
|
||||
- `app/services/llm_service.py` Zeile 102-123: `get_prompt()` mit Hierarchie
|
||||
- `app/core/retrieval/decision_engine.py` Zeile 107-113: Intent-Routing mit `prompt_key`
|
||||
- `app/core/retrieval/decision_engine.py` Zeile 309-315: Finale Synthese mit `prompt_key`
|
||||
|
||||
**Bewertung:** Prompt-Ladung funktioniert korrekt. Keine Regression.
|
||||
|
||||
---
|
||||
|
||||
## 3. Deduplizierungs-Logik
|
||||
|
||||
### Prüfpunkt: Gefährden die Änderungen an `all_chunk_callout_keys` in v4.5.7/8 die gewollte De-Duplizierung von Kanten (WP-24c)?
|
||||
|
||||
**Status:** ✅ **FUNKTIONIERT KORREKT**
|
||||
|
||||
**Ergebnis:**
|
||||
- `all_chunk_callout_keys` wird **VOR jeder Verwendung** initialisiert (Zeile 531-533)
|
||||
- Initialisierung erfolgt **VOR** Phase 1 (Sammeln aus `candidate_pool`) und **VOR** Phase 2 (Chunk-Verarbeitung)
|
||||
- Die De-Duplizierungs-Logik ist **vollständig intakt**:
|
||||
- Phase 1: Sammeln aller `explicit:callout` Keys aus `candidate_pool` (Zeile 657-697)
|
||||
- Phase 2: Prüfung gegen `all_chunk_callout_keys` vor Erstellung neuer Callout-Kanten (Zeile 768)
|
||||
- Globaler Scan: Nutzung von `all_chunk_callout_keys` als Ausschlusskriterium (Zeile 855)
|
||||
- LLM-Validierungs-Zonen: Callouts werden korrekt zu `all_chunk_callout_keys` hinzugefügt (Zeile 615)
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/core/graph/graph_derive_edges.py` Zeile 531-533: Initialisierung
|
||||
- `app/core/graph/graph_derive_edges.py` Zeile 657-697: Phase 1 (Sammeln)
|
||||
- `app/core/graph/graph_derive_edges.py` Zeile 768: Phase 2 (Prüfung)
|
||||
- `app/core/graph/graph_derive_edges.py` Zeile 855: Globaler Scan (Ausschluss)
|
||||
|
||||
**Bewertung:** De-Duplizierungs-Logik ist intakt. Keine Regression.
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase 3 Validierungs-Gate
|
||||
|
||||
### Prüfpunkt: Ist das Phase 3 Validierungs-Gate korrekt implementiert und nutzt es die MoE-Kaskade?
|
||||
|
||||
**Status:** ✅ **GEWOLLTE ÄNDERUNG** (v4.5.8)
|
||||
|
||||
**Ergebnis:**
|
||||
- Phase 3 Validierung ist **korrekt implementiert** in `ingestion_processor.py` (Zeile 274-371)
|
||||
- **Trigger-Kriterium:** Kanten mit `rule_id` ODER `provenance` beginnend mit `"candidate:"` (Zeile 292)
|
||||
- **Validierung:** Nutzt `validate_edge_candidate()` mit `profile_name="ingest_validator"` (Zeile 340-346)
|
||||
- **Erfolg:** Entfernt `candidate:` Präfix aus `rule_id` und `provenance` (Zeile 349-357)
|
||||
- **Ablehnung:** Kanten werden zu `rejected_edges` hinzugefügt und **nicht** weiterverarbeitet (Zeile 362-363)
|
||||
- **MoE-Kaskade:** Wird vollständig genutzt via `llm_service.generate_raw_response()` (siehe Prüfpunkt 2b)
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/core/ingestion/ingestion_processor.py` Zeile 274-371: Phase 3 Implementierung
|
||||
- `app/core/ingestion/ingestion_validation.py` Zeile 24-91: `validate_edge_candidate()`
|
||||
|
||||
**Bewertung:** Phase 3 Validierungs-Gate ist korrekt implementiert. **Gewollte Änderung**, keine Regression.
|
||||
|
||||
---
|
||||
|
||||
## 5. Note-Scope Kontext-Optimierung
|
||||
|
||||
### Prüfpunkt: Ist die Note-Scope Kontext-Optimierung korrekt implementiert?
|
||||
|
||||
**Status:** ✅ **GEWOLLTE ÄNDERUNG** (v4.5.8)
|
||||
|
||||
**Ergebnis:**
|
||||
- Kontext-Optimierung ist **korrekt implementiert** in Phase 3 Validierung (Zeile 311-326)
|
||||
- **Note-Scope:** Verwendet `note_summary` oder `note_text` (aggregierter Kontext) (Zeile 314-316)
|
||||
- **Chunk-Scope:** Versucht spezifischen Chunk-Text zu finden, sonst Note-Text (Zeile 318-326)
|
||||
- **Note-Summary:** Wird aus Top 5 Chunks erstellt (Zeile 282)
|
||||
- **Note-Text:** Wird aus `markdown_body` oder aggregiert aus allen Chunks erstellt (Zeile 280)
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/core/ingestion/ingestion_processor.py` Zeile 278-282: Note-Summary/Text Erstellung
|
||||
- `app/core/ingestion/ingestion_processor.py` Zeile 311-326: Kontext-Optimierung
|
||||
|
||||
**Bewertung:** Note-Scope Kontext-Optimierung ist korrekt implementiert. **Gewollte Änderung**, keine Regression.
|
||||
|
||||
---
|
||||
|
||||
## 6. Weitere Prüfungen
|
||||
|
||||
### 6.1 Edge-Registry Integration
|
||||
|
||||
**Status:** ✅ **FUNKTIONIERT KORREKT**
|
||||
|
||||
**Ergebnis:**
|
||||
- Edge-Registry wird korrekt für Typ-Auflösung genutzt (Zeile 383 in `ingestion_processor.py`)
|
||||
- Symmetrie-Generierung nutzt `edge_registry.get_inverse()` (Zeile 397)
|
||||
- Keine Regression festgestellt
|
||||
|
||||
### 6.2 Context-Reuse Logik
|
||||
|
||||
**Status:** ✅ **FUNKTIONIERT KORREKT**
|
||||
|
||||
**Ergebnis:**
|
||||
- Context-Reuse ist in `decision_engine.py` implementiert (Zeile 154-196)
|
||||
- Bei Kompressions-Fehlern wird Original-Content zurückgegeben (Zeile 232-235)
|
||||
- Bei Synthese-Fehlern wird Fallback mit vorhandenem Context genutzt (Zeile 328-365)
|
||||
- Keine Regression festgestellt
|
||||
|
||||
### 6.3 Prompt-Template Validierung
|
||||
|
||||
**Status:** ✅ **FUNKTIONIERT KORREKT**
|
||||
|
||||
**Ergebnis:**
|
||||
- Prompt-Validierung in `llm_service.py` prüft auf leere Templates (Zeile 172-175)
|
||||
- Fehlerbehandlung vorhanden: `ValueError` bei fehlendem oder leerem `prompt_key`
|
||||
- Keine Regression festgestellt
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
### ✅ Keine Regressionen festgestellt
|
||||
|
||||
Alle geprüften Funktionen arbeiten korrekt und entsprechen den ursprünglichen WP-Spezifikationen:
|
||||
|
||||
1. **WP-22 Scoring:** Mathematik bleibt unangetastet ✅
|
||||
2. **WP-25a/b MoE & Prompts:** Profile und Prompts werden korrekt geladen, MoE-Kaskade funktioniert ✅
|
||||
3. **Deduplizierungs-Logik:** `all_chunk_callout_keys` funktioniert korrekt ✅
|
||||
4. **Phase 3 Validierung:** Korrekt implementiert, nutzt MoE-Kaskade ✅
|
||||
5. **Note-Scope Kontext-Optimierung:** Korrekt implementiert ✅
|
||||
|
||||
### 📋 Gewollte Änderungen (v4.5.8)
|
||||
|
||||
Die folgenden Änderungen sind **explizit gewollt** und stellen keine Regressionen dar:
|
||||
|
||||
1. **Phase 3 Validierungs-Gate:** Neue Validierungs-Logik für `candidate:` Kanten
|
||||
2. **Note-Scope Kontext-Optimierung:** Optimierte Kontext-Auswahl für Note-Scope vs. Chunk-Scope Kanten
|
||||
|
||||
### 🔍 Empfehlungen
|
||||
|
||||
**Keine kritischen Probleme gefunden.** Das System ist in einem stabilen Zustand.
|
||||
|
||||
**Optional (nicht kritisch):**
|
||||
- Erwägen Sie zusätzliche Unit-Tests für Phase 3 Validierung
|
||||
- Dokumentation der `candidate:` → `verified` Transformation könnte erweitert werden
|
||||
|
||||
---
|
||||
|
||||
## Audit-Methodik
|
||||
|
||||
1. **Code-Analyse:** Vollständige Analyse der relevanten Dateien
|
||||
2. **Semantic Search:** Suche nach Verwendungen von `candidate:`, `verified`, `all_chunk_callout_keys`
|
||||
3. **Grep-Suche:** Exakte String-Suche nach kritischen Patterns
|
||||
4. **Dokumentations-Review:** Prüfung der technischen Dokumentation
|
||||
|
||||
**Geprüfte Dateien:**
|
||||
- `app/core/retrieval/retriever_scoring.py`
|
||||
- `app/services/llm_service.py`
|
||||
- `app/core/retrieval/decision_engine.py`
|
||||
- `app/core/graph/graph_derive_edges.py`
|
||||
- `app/core/ingestion/ingestion_processor.py`
|
||||
- `app/core/ingestion/ingestion_validation.py`
|
||||
- `config/prompts.yaml`
|
||||
- `config/llm_profiles.yaml`
|
||||
|
||||
---
|
||||
|
||||
## 7. Zusätzliche Prüfungen & Bekannte Schwachstellen
|
||||
|
||||
### 7.1 Callout-Extraktion aus Edge-Zonen (aus AUDIT_CLEAN_CONTEXT_V4.2.0)
|
||||
|
||||
**Status:** ⚠️ **POTENZIELL BEHOBEN** (verifizieren erforderlich)
|
||||
|
||||
**Hintergrund:**
|
||||
- AUDIT_CLEAN_CONTEXT_V4.2.0 identifizierte ein kritisches Problem: Callouts in Edge-Zonen wurden nicht extrahiert
|
||||
- Problem: Callouts wurden nur aus gefilterten Chunks extrahiert, nicht aus Original-Markdown
|
||||
|
||||
**Aktueller Status:**
|
||||
- ✅ Funktion `extract_callouts_from_markdown()` existiert in `graph_derive_edges.py` (Zeile 263-501)
|
||||
- ✅ Funktion wird in `build_edges_for_note()` aufgerufen (Zeile 852-864)
|
||||
- ⚠️ **VERIFIZIERUNG ERFORDERLICH:** Prüfen, ob Callouts in LLM-Validierungs-Zonen korrekt extrahiert werden
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/core/graph/graph_derive_edges.py` Zeile 263-501: `extract_callouts_from_markdown()`
|
||||
- `app/core/graph/graph_derive_edges.py` Zeile 852-864: Aufruf in `build_edges_for_note()`
|
||||
|
||||
**Empfehlung:**
|
||||
- Test mit Callout in LLM-Validierungs-Zone durchführen
|
||||
- Verifizieren, dass Edge in Qdrant `_edges` Collection existiert
|
||||
- Prüfen, ob `candidate:` Präfix korrekt gesetzt wird
|
||||
|
||||
---
|
||||
|
||||
### 7.2 Rejected Edges Tracking & Monitoring
|
||||
|
||||
**Status:** ⚠️ **POTENZIELLE SCHWACHSTELLE**
|
||||
|
||||
**Problem:**
|
||||
- Phase 3 Validierung lehnt Kanten ab und fügt sie zu `rejected_edges` hinzu (Zeile 363)
|
||||
- `rejected_edges` werden geloggt, aber **nicht persistiert** oder analysiert
|
||||
- Keine Möglichkeit, abgelehnte Kanten zu überprüfen oder zu debuggen
|
||||
|
||||
**Konsequenz:**
|
||||
- **Fehlende Transparenz:** Keine Nachvollziehbarkeit, warum Kanten abgelehnt wurden
|
||||
- **Keine Metriken:** Keine Statistiken über Ablehnungsrate
|
||||
- **Schwieriges Debugging:** Bei Problemen keine Möglichkeit, abgelehnte Kanten zu analysieren
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/core/ingestion/ingestion_processor.py` Zeile 363: `rejected_edges.append(e)`
|
||||
- `app/core/ingestion/ingestion_processor.py` Zeile 370-371: Logging, aber keine Persistierung
|
||||
|
||||
**Empfehlung:**
|
||||
- Optional: Persistierung von `rejected_edges` in Log-Datei oder separater Collection
|
||||
- Metriken: Tracking der Ablehnungsrate pro Note/Typ
|
||||
- Debug-Modus: Detailliertes Logging der Ablehnungsgründe
|
||||
|
||||
---
|
||||
|
||||
### 7.3 Transiente vs. Permanente Fehler in Phase 3 Validierung
|
||||
|
||||
**Status:** ✅ **FUNKTIONIERT KORREKT**
|
||||
|
||||
**Ergebnis:**
|
||||
- `validate_edge_candidate()` unterscheidet korrekt zwischen transienten und permanenten Fehlern (Zeile 79-91)
|
||||
- Transiente Fehler (Netzwerk) → Kante wird erlaubt (Integrität vor Präzision)
|
||||
- Permanente Fehler → Kante wird abgelehnt (Graph-Qualität schützen)
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/core/ingestion/ingestion_validation.py` Zeile 79-91: Fehlerbehandlung
|
||||
|
||||
**Bewertung:** Korrekt implementiert. Keine Regression.
|
||||
|
||||
---
|
||||
|
||||
### 7.4 Note-Scope Kontext-Optimierung: Chunk-Text Fallback
|
||||
|
||||
**Status:** ⚠️ **POTENZIELLE SCHWACHSTELLE**
|
||||
|
||||
**Problem:**
|
||||
- Bei Chunk-Scope Kanten wird versucht, spezifischen Chunk-Text zu finden (Zeile 319-325)
|
||||
- Fallback auf `note_text`, wenn Chunk-Text nicht gefunden wird
|
||||
- **Risiko:** Bei fehlendem Chunk-Text wird Note-Text verwendet, was weniger präzise ist
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/core/ingestion/ingestion_processor.py` Zeile 318-326: Chunk-Text Suche
|
||||
|
||||
**Empfehlung:**
|
||||
- Prüfen, ob Chunk-Text immer verfügbar ist
|
||||
- Bei fehlendem Chunk-Text: Warnung loggen
|
||||
- Optional: Bessere Fehlerbehandlung für fehlende Chunk-IDs
|
||||
|
||||
---
|
||||
|
||||
### 7.5 LLM-Validierungs-Zonen: Callout-Key Tracking
|
||||
|
||||
**Status:** ✅ **FUNKTIONIERT KORREKT**
|
||||
|
||||
**Ergebnis:**
|
||||
- Callouts aus LLM-Validierungs-Zonen werden korrekt zu `all_chunk_callout_keys` hinzugefügt (Zeile 615)
|
||||
- Verhindert Duplikate im globalen Scan
|
||||
- Korrekte `candidate:` Präfix-Setzung
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/core/graph/graph_derive_edges.py` Zeile 604-616: LLM-Validierungs-Zone Callout-Tracking
|
||||
|
||||
**Bewertung:** Korrekt implementiert. Keine Regression.
|
||||
|
||||
---
|
||||
|
||||
### 7.6 Scope-Aware Edge Retrieval (aus AUDIT_RETRIEVER_V4.1.0)
|
||||
|
||||
**Status:** ⚠️ **POTENZIELL BEHOBEN** (verifizieren erforderlich)
|
||||
|
||||
**Hintergrund:**
|
||||
- AUDIT_RETRIEVER_V4.1.0 identifizierte ein Problem: Retriever suchte nur nach Note-Level Edges, nicht Chunk-Level
|
||||
- Problem: Chunk-Scope Edges wurden nicht explizit berücksichtigt
|
||||
|
||||
**Aktueller Status:**
|
||||
- ⚠️ **VERIFIZIERUNG ERFORDERLICH:** Prüfen, ob `fetch_edges_from_qdrant` Chunk-Level Edges korrekt lädt
|
||||
- Dokumentation besagt, dass Optimierungen implementiert wurden
|
||||
|
||||
**Empfehlung:**
|
||||
- Test mit Chunk-Scope Edge durchführen
|
||||
- Verifizieren, dass Edge im Retrieval-Ergebnis enthalten ist
|
||||
- Prüfen, ob `chunk_id` Filter korrekt funktioniert
|
||||
|
||||
---
|
||||
|
||||
### 7.7 Section-Filtering im Retrieval (aus AUDIT_RETRIEVER_V4.1.0)
|
||||
|
||||
**Status:** ⚠️ **POTENZIELL BEHOBEN** (verifizieren erforderlich)
|
||||
|
||||
**Hintergrund:**
|
||||
- AUDIT_RETRIEVER_V4.1.0 identifizierte fehlende Filterung nach `target_section`
|
||||
- Problem: Section-Links (`[[Note#Section]]`) wurden nicht präzise gefiltert
|
||||
|
||||
**Aktueller Status:**
|
||||
- ⚠️ **VERIFIZIERUNG ERFORDERLICH:** Prüfen, ob `target_section` Filter im Retrieval funktioniert
|
||||
- Dokumentation besagt, dass Optimierungen implementiert wurden
|
||||
|
||||
**Empfehlung:**
|
||||
- Test mit Section-Link durchführen
|
||||
- Verifizieren, dass nur relevante Chunks zurückgegeben werden
|
||||
- Prüfen, ob `QueryRequest.target_section` korrekt verwendet wird
|
||||
|
||||
---
|
||||
|
||||
### 7.8 Prompt-Integration: Explanation Layer
|
||||
|
||||
**Status:** ⚠️ **UNKLAR** (aus AUDIT_CLEAN_CONTEXT_V4.2.0)
|
||||
|
||||
**Problem:**
|
||||
- Unklar, ob `explanation.related_edges` im LLM-Prompt verwendet werden
|
||||
- Keine explizite Dokumentation der Prompt-Struktur für RAG-Kontext
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/core/retrieval/retriever.py` Zeile 150-252: `_build_explanation()`
|
||||
- `app/routers/chat.py`: Prompt-Verwendung
|
||||
|
||||
**Empfehlung:**
|
||||
- Prüfen Sie `config/prompts.yaml` für `interview_template` und andere Templates
|
||||
- Stellen Sie sicher, dass `{related_edges}` oder ähnliche Variablen im Prompt verwendet werden
|
||||
- Dokumentieren Sie die Prompt-Struktur für RAG-Kontext
|
||||
|
||||
---
|
||||
|
||||
### 7.9 Fallback-Synthese: Hardcodierter Prompt (aus AUDIT_WP25B_CODE_REVIEW)
|
||||
|
||||
**Status:** ⚠️ **ARCHITEKTONISCHE INKONSISTENZ**
|
||||
|
||||
**Problem:**
|
||||
- Fallback-Synthese in `decision_engine.py` verwendet `prompt=` statt `prompt_key=` (Zeile 361)
|
||||
- Inkonsistent mit WP25b-Architektur (Lazy-Loading)
|
||||
- Keine modell-spezifischen Prompts im Fallback
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/core/retrieval/decision_engine.py` Zeile 360-363: Hardcodierter Prompt
|
||||
|
||||
**Empfehlung:**
|
||||
- Umstellen auf `prompt_key="fallback_synthesis"` mit `variables`
|
||||
- Konsistenz mit WP25b-Architektur
|
||||
- Modell-spezifische Optimierungen auch im Fallback
|
||||
|
||||
**Schweregrad:** 🟡 Mittel (funktional, aber architektonisch inkonsistent)
|
||||
|
||||
---
|
||||
|
||||
### 7.10 Edge-Registry: Unbekannte Kanten
|
||||
|
||||
**Status:** ✅ **FUNKTIONIERT KORREKT**
|
||||
|
||||
**Ergebnis:**
|
||||
- Unbekannte Kanten-Typen werden in `unknown_edges.jsonl` protokolliert
|
||||
- Edge-Registry normalisiert Kanten-Typen korrekt
|
||||
- Keine Regression festgestellt
|
||||
|
||||
**Code-Referenz:**
|
||||
- `app/services/edge_registry.py`: Edge-Registry Implementierung
|
||||
|
||||
**Bewertung:** Korrekt implementiert. Keine Regression.
|
||||
|
||||
---
|
||||
|
||||
## 8. Zusammenfassung der zusätzlichen Prüfungen
|
||||
|
||||
### ✅ Bestätigt funktionierend:
|
||||
1. **Transiente vs. Permanente Fehler:** Korrekte Unterscheidung ✅
|
||||
2. **LLM-Validierungs-Zonen Callout-Tracking:** Korrekt implementiert ✅
|
||||
3. **Edge-Registry:** Funktioniert korrekt ✅
|
||||
|
||||
### ⚠️ Verifizierung erforderlich:
|
||||
1. **Callout-Extraktion aus Edge-Zonen:** Funktion existiert, aber Verifizierung erforderlich
|
||||
2. **Scope-Aware Edge Retrieval:** Potenziell behoben, Verifizierung erforderlich
|
||||
3. **Section-Filtering:** Potenziell behoben, Verifizierung erforderlich
|
||||
|
||||
### ⚠️ Potenzielle Schwachstellen:
|
||||
1. **Rejected Edges Tracking:** Keine Persistierung oder Metriken
|
||||
2. **Note-Scope Kontext-Optimierung:** Chunk-Text Fallback könnte verbessert werden
|
||||
3. **Prompt-Integration:** Unklar, ob `explanation.related_edges` verwendet werden
|
||||
4. **Fallback-Synthese:** Architektonische Inkonsistenz (hardcodierter Prompt)
|
||||
|
||||
---
|
||||
|
||||
## 9. Empfohlene Follow-up Prüfungen
|
||||
|
||||
### 9.1 Funktionale Tests
|
||||
|
||||
1. **Callout in LLM-Validierungs-Zone:**
|
||||
- Erstellen Sie eine Notiz mit Callout in `### Unzugeordnete Kanten`
|
||||
- Verifizieren: Edge existiert in Qdrant mit `candidate:` Präfix
|
||||
- Verifizieren: Edge wird in Phase 3 validiert
|
||||
|
||||
2. **Chunk-Scope Edge Retrieval:**
|
||||
- Erstellen Sie eine Note mit Chunk-Scope Edge
|
||||
- Query mit `explain=True`
|
||||
- Verifizieren: Edge erscheint in `explanation.related_edges`
|
||||
|
||||
3. **Section-Link Retrieval:**
|
||||
- Erstellen Sie einen Section-Link (`[[Note#Section]]`)
|
||||
- Query mit `target_section="Section"`
|
||||
- Verifizieren: Nur relevante Chunks werden zurückgegeben
|
||||
|
||||
### 9.2 Metriken & Monitoring
|
||||
|
||||
1. **Phase 3 Validierung Metriken:**
|
||||
- Tracking der Validierungsrate (verified/rejected)
|
||||
- Tracking der Ablehnungsgründe
|
||||
- Monitoring der LLM-Validierungs-Performance
|
||||
|
||||
2. **Edge-Statistiken:**
|
||||
- Anzahl der `candidate:` Kanten pro Note
|
||||
- Anzahl der verifizierten Kanten pro Note
|
||||
- Anzahl der abgelehnten Kanten pro Note
|
||||
|
||||
### 9.3 Dokumentation
|
||||
|
||||
1. **Prompt-Struktur:**
|
||||
- Dokumentieren Sie die Verwendung von `explanation.related_edges` in Prompts
|
||||
- Erstellen Sie Beispiele für RAG-Kontext-Integration
|
||||
|
||||
2. **Phase 3 Validierung:**
|
||||
- Dokumentieren Sie den Validierungs-Prozess
|
||||
- Erstellen Sie Troubleshooting-Guide für abgelehnte Kanten
|
||||
|
||||
---
|
||||
|
||||
**Audit abgeschlossen:** ✅ System-Integrität bestätigt mit zusätzlichen Prüfungen
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
# Debug: .env-Lade-Problem in Prod
|
||||
|
||||
**Datum**: 2026-01-12
|
||||
**Version**: v4.5.10
|
||||
**Status**: 🔴 Kritisch
|
||||
|
||||
## Problem
|
||||
|
||||
Möglicherweise wird die `.env`-Datei in Prod nicht korrekt geladen, was zu:
|
||||
- Falschen Log-Levels (DEBUG=true wird ignoriert)
|
||||
- Falschen Collection-Präfixen
|
||||
- Falschen Konfigurationen
|
||||
führen kann.
|
||||
|
||||
## Diagnose
|
||||
|
||||
### Schritt 1: Prüfe, ob .env-Datei existiert
|
||||
|
||||
```bash
|
||||
# In Prod
|
||||
cd ~/mindnet
|
||||
ls -la .env
|
||||
cat .env | head -20
|
||||
```
|
||||
|
||||
### Schritt 2: Prüfe Arbeitsverzeichnis beim Start
|
||||
|
||||
```bash
|
||||
# In Prod - prüfe, von wo uvicorn gestartet wird
|
||||
ps aux | grep uvicorn
|
||||
# Oder in systemd service:
|
||||
cat /etc/systemd/system/mindnet.service | grep WorkingDirectory
|
||||
```
|
||||
|
||||
### Schritt 3: Verifikations-Script ausführen
|
||||
|
||||
```bash
|
||||
# In Prod
|
||||
cd ~/mindnet
|
||||
source .venv/bin/activate
|
||||
python3 scripts/verify_env_loading.py
|
||||
```
|
||||
|
||||
**Erwartete Ausgabe**:
|
||||
```
|
||||
✅ .env geladen von: /path/to/mindnet/.env
|
||||
✅ COLLECTION_PREFIX = mindnet
|
||||
✅ DEBUG = true
|
||||
```
|
||||
|
||||
### Schritt 4: Manuelle Verifikation
|
||||
|
||||
```python
|
||||
# In Python-REPL in Prod
|
||||
import os
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Prüfe aktuelles Verzeichnis
|
||||
print(f"CWD: {Path.cwd()}")
|
||||
print(f"Projekt-Root: {Path(__file__).parent.parent.parent}")
|
||||
|
||||
# Lade .env
|
||||
env_file = Path(".env")
|
||||
if env_file.exists():
|
||||
load_dotenv(env_file, override=True)
|
||||
print(f"✅ .env geladen: {env_file.absolute()}")
|
||||
else:
|
||||
print(f"❌ .env nicht gefunden in: {env_file.absolute()}")
|
||||
|
||||
# Prüfe kritische Variablen
|
||||
print(f"DEBUG: {os.getenv('DEBUG', 'NICHT GESETZT')}")
|
||||
print(f"COLLECTION_PREFIX: {os.getenv('COLLECTION_PREFIX', 'NICHT GESETZT')}")
|
||||
```
|
||||
|
||||
## Mögliche Ursachen
|
||||
|
||||
### 1. Arbeitsverzeichnis-Problem
|
||||
- **Problem**: uvicorn wird aus einem anderen Verzeichnis gestartet
|
||||
- **Lösung**: Expliziter Pfad in `config.py` (bereits implementiert)
|
||||
|
||||
### 2. .env-Datei nicht im Projekt-Root
|
||||
- **Problem**: .env liegt in `config/prod.env` statt `.env`
|
||||
- **Lösung**: Symlink erstellen oder Pfad anpassen
|
||||
|
||||
### 3. Systemd-Service ohne WorkingDirectory
|
||||
- **Problem**: Service startet ohne korrektes Arbeitsverzeichnis
|
||||
- **Lösung**: `WorkingDirectory=/path/to/mindnet` in systemd service
|
||||
|
||||
### 4. Mehrere .env-Dateien
|
||||
- **Problem**: Es gibt `.env`, `prod.env`, `config/prod.env` - welche wird geladen?
|
||||
- **Lösung**: Expliziter Pfad oder Umgebungsvariable `DOTENV_PATH`
|
||||
|
||||
## Fix-Implementierung
|
||||
|
||||
Der Code in `app/config.py` wurde erweitert:
|
||||
- ✅ Expliziter Pfad für `.env` im Projekt-Root
|
||||
- ✅ Fallback auf automatische Suche
|
||||
- ✅ Debug-Logging (wenn verfügbar)
|
||||
|
||||
## Verifikation nach Fix
|
||||
|
||||
1. **Log prüfen**: Sollte `✅ .env geladen von: ...` zeigen
|
||||
2. **Umgebungsvariablen prüfen**: `echo $DEBUG`, `echo $COLLECTION_PREFIX`
|
||||
3. **Settings prüfen**: `python3 -c "from app.config import get_settings; s = get_settings(); print(f'DEBUG: {s.DEBUG}, PREFIX: {s.COLLECTION_PREFIX}')"`
|
||||
|
|
@ -1,242 +0,0 @@
|
|||
# Konfiguration von Edge-Zonen Headern (v4.2.0)
|
||||
|
||||
**Version:** v4.2.0
|
||||
**Status:** Aktiv
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Mindnet-System unterstützt zwei Arten von speziellen Markdown-Sektionen für Kanten:
|
||||
|
||||
1. **LLM-Validierung Zonen** - Links, die vom LLM validiert werden
|
||||
2. **Note-Scope Zonen** - Links, die der gesamten Note zugeordnet werden
|
||||
|
||||
Die Header-Namen für beide Zonen-Typen sind über Umgebungsvariablen konfigurierbar.
|
||||
|
||||
## Konfiguration via .env
|
||||
|
||||
### LLM-Validierung Header
|
||||
|
||||
**Umgebungsvariablen:**
|
||||
- `MINDNET_LLM_VALIDATION_HEADERS` - Komma-separierte Liste von Header-Namen
|
||||
- `MINDNET_LLM_VALIDATION_HEADER_LEVEL` - Header-Ebene (1-6, Default: 3 für `###`)
|
||||
|
||||
**Format:** Komma-separierte Liste von Header-Namen
|
||||
|
||||
**Default:**
|
||||
```
|
||||
MINDNET_LLM_VALIDATION_HEADERS=Unzugeordnete Kanten,Edge Pool,Candidates
|
||||
MINDNET_LLM_VALIDATION_HEADER_LEVEL=3
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```env
|
||||
MINDNET_LLM_VALIDATION_HEADERS=Unzugeordnete Kanten,Edge Pool,Candidates,Zu prüfende Links
|
||||
MINDNET_LLM_VALIDATION_HEADER_LEVEL=3
|
||||
```
|
||||
|
||||
**Verwendung in Markdown:**
|
||||
```markdown
|
||||
### Unzugeordnete Kanten
|
||||
|
||||
related_to:Ziel-Notiz
|
||||
depends_on:Andere Notiz
|
||||
```
|
||||
|
||||
**Wichtig:** Diese Bereiche werden **nicht als Chunks angelegt**, sondern nur die Kanten extrahiert.
|
||||
|
||||
### Note-Scope Zone Header
|
||||
|
||||
**Umgebungsvariablen:**
|
||||
- `MINDNET_NOTE_SCOPE_ZONE_HEADERS` - Komma-separierte Liste von Header-Namen
|
||||
- `MINDNET_NOTE_SCOPE_HEADER_LEVEL` - Header-Ebene (1-6, Default: 2 für `##`)
|
||||
|
||||
**Format:** Komma-separierte Liste von Header-Namen
|
||||
|
||||
**Default:**
|
||||
```
|
||||
MINDNET_NOTE_SCOPE_ZONE_HEADERS=Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen
|
||||
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```env
|
||||
MINDNET_NOTE_SCOPE_ZONE_HEADERS=Smart Edges,Relationen,Globale Verbindungen,Note-Level Links
|
||||
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
|
||||
```
|
||||
|
||||
**Verwendung in Markdown:**
|
||||
```markdown
|
||||
## Smart Edges
|
||||
|
||||
[[rel:depends_on|Globale Notiz]]
|
||||
[[rel:part_of|System-Übersicht]]
|
||||
```
|
||||
|
||||
**Wichtig:** Diese Bereiche werden **nicht als Chunks angelegt**, sondern nur die Kanten extrahiert.
|
||||
|
||||
## Konfiguration in prod.env
|
||||
|
||||
Fügen Sie die folgenden Zeilen zu Ihrer `.env` oder `config/prod.env` hinzu:
|
||||
|
||||
```env
|
||||
# --- WP-24c v4.2.0: Konfigurierbare Markdown-Header für Edge-Zonen ---
|
||||
# Komma-separierte Liste von Headern für LLM-Validierung
|
||||
MINDNET_LLM_VALIDATION_HEADERS=Unzugeordnete Kanten,Edge Pool,Candidates
|
||||
|
||||
# Header-Ebene für LLM-Validierung (1-6, Default: 3 für ###)
|
||||
MINDNET_LLM_VALIDATION_HEADER_LEVEL=3
|
||||
|
||||
# Komma-separierte Liste von Headern für Note-Scope Zonen
|
||||
MINDNET_NOTE_SCOPE_ZONE_HEADERS=Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen
|
||||
|
||||
# Header-Ebene für Note-Scope Zonen (1-6, Default: 2 für ##)
|
||||
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
|
||||
```
|
||||
|
||||
**Wichtig:** Beide Zonen-Typen werden **nicht als Chunks angelegt**. Nur die Kanten werden extrahiert, der Text selbst wird vom Chunking ausgeschlossen.
|
||||
|
||||
## Unterschiede
|
||||
|
||||
### LLM-Validierung Zonen
|
||||
|
||||
- **Header-Ebene:** Konfigurierbar via `MINDNET_LLM_VALIDATION_HEADER_LEVEL` (Default: 3 = `###`)
|
||||
- **Zweck:** Links werden vom LLM validiert
|
||||
- **Provenance:** `global_pool`
|
||||
- **Scope:** `chunk` (wird Chunks zugeordnet)
|
||||
- **Aktivierung:** Nur wenn `enable_smart_edge_allocation: true`
|
||||
- **Chunking:** ❌ **Diese Bereiche werden NICHT als Chunks angelegt** - nur Kanten werden extrahiert
|
||||
|
||||
**Beispiel:**
|
||||
```markdown
|
||||
### Unzugeordnete Kanten
|
||||
|
||||
related_to:Mögliche Verbindung
|
||||
depends_on:Unsichere Notiz
|
||||
```
|
||||
|
||||
### Note-Scope Zonen
|
||||
|
||||
- **Header-Ebene:** Konfigurierbar via `MINDNET_NOTE_SCOPE_HEADER_LEVEL` (Default: 2 = `##`)
|
||||
- **Zweck:** Links werden der gesamten Note zugeordnet
|
||||
- **Provenance:** `explicit:note_zone`
|
||||
- **Scope:** `note` (Note-weite Verbindung)
|
||||
- **Aktivierung:** Immer aktiv
|
||||
- **Chunking:** ❌ **Diese Bereiche werden NICHT als Chunks angelegt** - nur Kanten werden extrahiert
|
||||
|
||||
**Beispiel:**
|
||||
```markdown
|
||||
## Smart Edges
|
||||
|
||||
[[rel:depends_on|Globale Notiz]]
|
||||
[[rel:part_of|System-Übersicht]]
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Empfohlen
|
||||
|
||||
1. **Konsistente Header-Namen:**
|
||||
- Nutzen Sie aussagekräftige Namen
|
||||
- Dokumentieren Sie die verwendeten Header in Ihrem Team
|
||||
|
||||
2. **Minimale Konfiguration:**
|
||||
- Nutzen Sie die Defaults, wenn möglich
|
||||
- Nur bei Bedarf anpassen
|
||||
|
||||
3. **Dokumentation:**
|
||||
- Dokumentieren Sie benutzerdefinierte Header in Ihrer Projekt-Dokumentation
|
||||
|
||||
### ❌ Vermeiden
|
||||
|
||||
1. **Zu viele Header:**
|
||||
- Zu viele Optionen können verwirrend sein
|
||||
- Beschränken Sie sich auf 3-5 Header pro Typ
|
||||
|
||||
2. **Ähnliche Namen:**
|
||||
- Vermeiden Sie Header, die sich zu ähnlich sind
|
||||
- Klare Unterscheidung zwischen LLM-Validierung und Note-Scope
|
||||
|
||||
## Technische Details
|
||||
|
||||
### Code-Referenzen
|
||||
|
||||
- **LLM-Validierung:** `app/core/chunking/chunking_processor.py` (Zeile 66-72)
|
||||
- **Note-Scope Zonen:** `app/core/graph/graph_derive_edges.py` → `get_note_scope_zone_headers()`
|
||||
|
||||
### Fallback-Verhalten
|
||||
|
||||
- Wenn die Umgebungsvariable nicht gesetzt ist, werden die Defaults verwendet
|
||||
- Wenn die Variable leer ist, werden ebenfalls die Defaults verwendet
|
||||
- Header-Namen werden case-insensitive verglichen
|
||||
|
||||
### Regex-Escape
|
||||
|
||||
- Header-Namen werden automatisch für Regex escaped
|
||||
- Sonderzeichen in Header-Namen sind sicher
|
||||
|
||||
## Beispiel-Konfiguration
|
||||
|
||||
```env
|
||||
# Eigene Header-Namen für LLM-Validierung (H3)
|
||||
MINDNET_LLM_VALIDATION_HEADERS=Zu prüfende Links,Kandidaten,Edge Pool
|
||||
MINDNET_LLM_VALIDATION_HEADER_LEVEL=3
|
||||
|
||||
# Eigene Header-Namen für Note-Scope Zonen (H2)
|
||||
MINDNET_NOTE_SCOPE_ZONE_HEADERS=Globale Relationen,Note-Verbindungen,Smart Links
|
||||
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
|
||||
```
|
||||
|
||||
**Alternative:** Beide auf H2 setzen:
|
||||
```env
|
||||
MINDNET_LLM_VALIDATION_HEADER_LEVEL=2
|
||||
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
|
||||
```
|
||||
|
||||
**Verwendung:**
|
||||
```markdown
|
||||
---
|
||||
type: decision
|
||||
title: Meine Notiz
|
||||
---
|
||||
|
||||
# Inhalt
|
||||
|
||||
## Globale Relationen
|
||||
|
||||
[[rel:depends_on|System-Architektur]]
|
||||
|
||||
### Zu prüfende Links
|
||||
|
||||
related_to:Mögliche Verbindung
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Kann ich beide Zonen-Typen in einer Notiz verwenden?**
|
||||
A: Ja, beide können gleichzeitig verwendet werden.
|
||||
|
||||
**Q: Was passiert, wenn ein Header in beiden Listen steht?**
|
||||
A: Die Note-Scope Zone hat Vorrang (wird als Note-Scope behandelt).
|
||||
|
||||
**Q: Können Header-Namen Leerzeichen enthalten?**
|
||||
A: Ja, Leerzeichen werden beibehalten.
|
||||
|
||||
**Q: Werden Header-Namen case-sensitive verglichen?**
|
||||
A: Nein, der Vergleich ist case-insensitive.
|
||||
|
||||
**Q: Kann ich Header-Namen mit Sonderzeichen verwenden?**
|
||||
A: Ja, Sonderzeichen werden automatisch für Regex escaped.
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
- ✅ **LLM-Validierung:**
|
||||
- `MINDNET_LLM_VALIDATION_HEADERS` (Header-Namen, komma-separiert)
|
||||
- `MINDNET_LLM_VALIDATION_HEADER_LEVEL` (Header-Ebene 1-6, Default: 3)
|
||||
- ❌ **Nicht als Chunks angelegt** - nur Kanten werden extrahiert
|
||||
- ✅ **Note-Scope Zonen:**
|
||||
- `MINDNET_NOTE_SCOPE_ZONE_HEADERS` (Header-Namen, komma-separiert)
|
||||
- `MINDNET_NOTE_SCOPE_HEADER_LEVEL` (Header-Ebene 1-6, Default: 2)
|
||||
- ❌ **Nicht als Chunks angelegt** - nur Kanten werden extrahiert
|
||||
- ✅ **Format:** Komma-separierte Liste für Header-Namen
|
||||
- ✅ **Fallback:** Defaults werden verwendet, falls nicht konfiguriert
|
||||
- ✅ **Case-insensitive:** Header-Namen werden case-insensitive verglichen
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
# Deployment-Checkliste: Prod vs. Dev Retrieval-Problem
|
||||
|
||||
**Datum**: 2026-01-12
|
||||
**Version**: v4.5.10
|
||||
**Status**: 🔴 Kritisch
|
||||
|
||||
## Problem
|
||||
|
||||
Prod-System findet keine Suchergebnisse, während Dev-System korrekt funktioniert. Identischer Code, identische Daten.
|
||||
|
||||
## Identifizierte Ursachen
|
||||
|
||||
### 1. 🔴 **KRITISCH: Alte EdgeDTO-Version in Prod**
|
||||
|
||||
**Symptom**:
|
||||
```
|
||||
ERROR: 1 validation error for EdgeDTO
|
||||
provenance
|
||||
Input should be 'explicit', 'rule', 'smart' or 'structure'
|
||||
[type=literal_error, input_value='explicit:callout', input_type=str]
|
||||
```
|
||||
|
||||
**Ursache**:
|
||||
- Prod verwendet eine **alte Version** des `EdgeDTO`-Modells aus `app/models/dto.py`
|
||||
- Die alte Version unterstützt nur: `"explicit", "rule", "smart", "structure"`
|
||||
- Die neue Version (v4.5.3+) unterstützt: `"explicit:callout", "explicit:wikilink", "explicit:note_zone", ...`
|
||||
|
||||
**Lösung**:
|
||||
- ✅ Code in `dto.py` ist bereits korrekt (Zeile 51-56)
|
||||
- ⚠️ **Prod muss neu gestartet werden**, um die neue Version zu laden
|
||||
- ⚠️ **Python-Modul-Cache leeren** falls nötig: `find . -type d -name __pycache__ -exec rm -r {} +`
|
||||
|
||||
### 2. ✅ Collection-Präfix korrekt
|
||||
|
||||
- Prod: `COLLECTION_PREFIX=mindnet` → `mindnet_chunks` ✅
|
||||
- Dev: `COLLECTION_PREFIX=mindnet_dev` → `mindnet_dev_chunks` ✅
|
||||
- **Kein Problem hier**
|
||||
|
||||
## Sofortmaßnahmen
|
||||
|
||||
### Schritt 1: Code-Verifikation in Prod
|
||||
|
||||
```bash
|
||||
# In Prod-System
|
||||
cd /path/to/mindnet
|
||||
grep -A 10 "provenance.*Literal" app/models/dto.py
|
||||
```
|
||||
|
||||
**Erwartete Ausgabe**:
|
||||
```python
|
||||
provenance: Optional[Literal[
|
||||
"explicit", "rule", "smart", "structure",
|
||||
"explicit:callout", "explicit:wikilink", "explicit:note_zone", ...
|
||||
]] = "explicit"
|
||||
```
|
||||
|
||||
**Falls nicht vorhanden**: Code ist nicht aktualisiert → Deployment erforderlich
|
||||
|
||||
### Schritt 2: Python-Cache leeren
|
||||
|
||||
```bash
|
||||
# In Prod-System
|
||||
find . -type d -name __pycache__ -exec rm -r {} +
|
||||
find . -name "*.pyc" -delete
|
||||
```
|
||||
|
||||
### Schritt 3: Service neu starten
|
||||
|
||||
```bash
|
||||
# FastAPI/uvicorn neu starten
|
||||
# Oder Docker-Container neu starten
|
||||
```
|
||||
|
||||
### Schritt 4: Verifikation
|
||||
|
||||
1. **Test-Query ausführen**:
|
||||
```bash
|
||||
curl -X POST http://localhost:8001/api/chat \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"message": "Was für einen Status hat das Projekt mindnet?"}'
|
||||
```
|
||||
|
||||
2. **Log prüfen**:
|
||||
- ✅ Keine `validation error for EdgeDTO` mehr
|
||||
- ✅ `✨ [SUCCESS] Stream 'facts_stream' lieferte X Treffer.`
|
||||
- ✅ Ergebnisse werden zurückgegeben
|
||||
|
||||
## Code-Vergleich
|
||||
|
||||
### Aktuelle Version (sollte in Prod sein):
|
||||
|
||||
```python
|
||||
# app/models/dto.py (Zeile 51-56)
|
||||
provenance: Optional[Literal[
|
||||
"explicit", "rule", "smart", "structure",
|
||||
"explicit:callout", "explicit:wikilink", "explicit:note_zone", "explicit:note_scope",
|
||||
"inline:rel", "callout:edge", "semantic_ai", "structure:belongs_to", "structure:order",
|
||||
"derived:backlink", "edge_defaults", "global_pool"
|
||||
]] = "explicit"
|
||||
```
|
||||
|
||||
### Alte Version (verursacht Fehler):
|
||||
|
||||
```python
|
||||
# Alte Version (nur 4 Werte)
|
||||
provenance: Optional[Literal[
|
||||
"explicit", "rule", "smart", "structure"
|
||||
]] = "explicit"
|
||||
```
|
||||
|
||||
## Weitere mögliche Ursachen (wenn Fix nicht hilft)
|
||||
|
||||
### 1. Unterschiedliche Python-Versionen
|
||||
- Prüfen: `python --version` in Dev vs. Prod
|
||||
- Pydantic-Verhalten kann zwischen Versionen variieren
|
||||
|
||||
### 2. Unterschiedliche Pydantic-Versionen
|
||||
- Prüfen: `pip list | grep pydantic` in Dev vs. Prod
|
||||
- `requirements.txt` sollte identisch sein
|
||||
|
||||
### 3. Unterschiedliche Embedding-Modelle
|
||||
- Prüfen: `MINDNET_EMBEDDING_MODEL` in beiden Systemen
|
||||
- **Beide verwenden**: `nomic-embed-text` ✅
|
||||
|
||||
### 4. Unterschiedliche Vektor-Dimensionen
|
||||
- Prüfen: `VECTOR_DIM` in beiden Systemen
|
||||
- **Beide verwenden**: `768` ✅
|
||||
|
||||
## Erwartetes Ergebnis nach Fix
|
||||
|
||||
- ✅ Keine Pydantic-Validierungsfehler mehr
|
||||
- ✅ Alle Streams liefern Ergebnisse
|
||||
- ✅ Retrieval funktioniert identisch in Dev und Prod
|
||||
- ✅ `explicit:callout` Provenance wird korrekt akzeptiert
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
# Fix: Python-Modul-Cache-Problem in Prod
|
||||
|
||||
**Datum**: 2026-01-12
|
||||
**Version**: v4.5.10
|
||||
**Status**: 🔴 Kritisch
|
||||
|
||||
## Problem
|
||||
|
||||
Code in `app/models/dto.py` ist korrekt (enthält `explicit:callout`), aber Prod verwendet trotzdem eine alte Version.
|
||||
|
||||
**Symptom**:
|
||||
```
|
||||
ERROR: 1 validation error for EdgeDTO
|
||||
provenance
|
||||
Input should be 'explicit', 'rule', 'smart' or 'structure'
|
||||
[type=literal_error, input_value='explicit:callout', input_type=str]
|
||||
```
|
||||
|
||||
## Ursache
|
||||
|
||||
**Python-Modul-Cache**: Python speichert kompilierte `.pyc` Dateien in `__pycache__` Verzeichnissen. Wenn der Code aktualisiert wird, aber der Service nicht neu gestartet wird, lädt Python die alte gecachte Version.
|
||||
|
||||
## Sofortmaßnahmen
|
||||
|
||||
### Schritt 1: Python-Cache leeren
|
||||
|
||||
```bash
|
||||
# In Prod-System
|
||||
cd ~/mindnet
|
||||
|
||||
# Finde und lösche alle __pycache__ Verzeichnisse
|
||||
find . -type d -name __pycache__ -exec rm -r {} + 2>/dev/null || true
|
||||
|
||||
# Finde und lösche alle .pyc Dateien
|
||||
find . -name "*.pyc" -delete
|
||||
|
||||
# Speziell für dto.py
|
||||
rm -rf app/models/__pycache__
|
||||
rm -rf app/__pycache__
|
||||
rm -rf __pycache__
|
||||
```
|
||||
|
||||
### Schritt 2: Verifikation des Codes
|
||||
|
||||
```bash
|
||||
# Prüfe, ob der Code korrekt ist
|
||||
grep -A 10 "provenance.*Literal" app/models/dto.py | grep "explicit:callout"
|
||||
```
|
||||
|
||||
**Erwartete Ausgabe**: Sollte `explicit:callout` enthalten
|
||||
|
||||
### Schritt 3: Service neu starten
|
||||
|
||||
**Option A: FastAPI/uvicorn direkt**:
|
||||
```bash
|
||||
# Service stoppen (Ctrl+C oder kill)
|
||||
# Dann neu starten
|
||||
source .venv/bin/activate
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8001 --reload
|
||||
```
|
||||
|
||||
**Option B: Systemd-Service**:
|
||||
```bash
|
||||
sudo systemctl restart mindnet-prod
|
||||
# oder
|
||||
sudo systemctl restart mindnet
|
||||
```
|
||||
|
||||
**Option C: Docker-Container**:
|
||||
```bash
|
||||
docker-compose restart mindnet
|
||||
# oder
|
||||
docker restart mindnet-container
|
||||
```
|
||||
|
||||
### Schritt 4: Verifikation zur Laufzeit
|
||||
|
||||
**Test-Script ausführen** (wenn verfügbar):
|
||||
```bash
|
||||
python3 scripts/verify_dto_import.py
|
||||
```
|
||||
|
||||
**Erwartete Ausgabe**:
|
||||
```
|
||||
✅ EdgeDTO unterstützt 'explicit:callout'
|
||||
✅ 'explicit:callout' ist in der Literal-Liste enthalten
|
||||
✅ EdgeDTO mit 'explicit:callout' erfolgreich erstellt!
|
||||
```
|
||||
|
||||
**Oder manuell testen**:
|
||||
```python
|
||||
python3 -c "
|
||||
from app.models.dto import EdgeDTO
|
||||
test = EdgeDTO(
|
||||
id='test', kind='test', source='test', target='test',
|
||||
weight=1.0, provenance='explicit:callout'
|
||||
)
|
||||
print('✅ EdgeDTO mit explicit:callout funktioniert!')
|
||||
"
|
||||
```
|
||||
|
||||
## Code-Fix (Fallback-Mechanismus)
|
||||
|
||||
Ein Fallback-Mechanismus wurde in `retriever.py` implementiert:
|
||||
- Wenn `EdgeDTO` mit `explicit:callout` fehlschlägt, wird automatisch `explicit` als Fallback verwendet
|
||||
- Dies verhindert, dass der gesamte Retrieval-Prozess fehlschlägt
|
||||
- **WICHTIG**: Dies ist nur eine temporäre Lösung - der Cache muss trotzdem geleert werden!
|
||||
|
||||
## Verifikation nach Fix
|
||||
|
||||
1. **Test-Query ausführen**:
|
||||
```bash
|
||||
curl -X POST http://localhost:8001/api/chat \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"message": "Was für einen Status hat das Projekt mindnet?"}'
|
||||
```
|
||||
|
||||
2. **Log prüfen**:
|
||||
- ✅ Keine `validation error for EdgeDTO` mehr
|
||||
- ✅ Keine `⚠️ [EDGE-DTO] Provenance 'explicit:callout' nicht unterstützt` Warnungen
|
||||
- ✅ `✨ [SUCCESS] Stream 'facts_stream' lieferte X Treffer.`
|
||||
- ✅ Ergebnisse werden zurückgegeben
|
||||
|
||||
## Warum passiert das?
|
||||
|
||||
1. **Code wurde aktualisiert**, aber Service läuft noch mit alter Version im Speicher
|
||||
2. **Python lädt Module nur einmal** - nach dem ersten Import wird die gecachte Version verwendet
|
||||
3. **__pycache__ Verzeichnisse** enthalten kompilierte Bytecode-Versionen der alten Dateien
|
||||
|
||||
## Prävention
|
||||
|
||||
- **Immer Service neu starten** nach Code-Änderungen
|
||||
- **Cache regelmäßig leeren** bei Deployment
|
||||
- **Verwende `--reload` Flag** bei uvicorn für automatisches Neuladen (nur für Dev!)
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
# Analyse: Retrieval-Unterschiede zwischen Dev und Prod
|
||||
|
||||
**Datum**: 2026-01-12
|
||||
**Version**: v4.5.10
|
||||
**Status**: 🔴 Kritisch
|
||||
|
||||
## Problemstellung
|
||||
|
||||
Bei identischer Codebasis und identischen Daten liefert das Dev-System Suchergebnisse, während das Prod-System keine Ergebnisse findet.
|
||||
|
||||
## Identifizierte Ursachen
|
||||
|
||||
### 1. 🔴 **KRITISCH: Inkonsistente Collection-Präfix-Konfiguration**
|
||||
|
||||
**Problem**: Zwei verschiedene Umgebungsvariablen werden für den Collection-Präfix verwendet:
|
||||
|
||||
1. **`app/config.py` (Zeile 24)**:
|
||||
```python
|
||||
COLLECTION_PREFIX: str = os.getenv("MINDNET_PREFIX", "mindnet_dev")
|
||||
```
|
||||
- Verwendet `MINDNET_PREFIX` als Umgebungsvariable
|
||||
- Default: `"mindnet_dev"`
|
||||
|
||||
2. **`app/core/database/qdrant.py` (Zeile 47)**:
|
||||
```python
|
||||
prefix = os.getenv("COLLECTION_PREFIX") or "mindnet"
|
||||
```
|
||||
- Verwendet `COLLECTION_PREFIX` als Umgebungsvariable
|
||||
- Default: `"mindnet"`
|
||||
|
||||
**Auswirkung**:
|
||||
- **Retriever verwendet `QdrantConfig.from_env()`**, das `COLLECTION_PREFIX` liest
|
||||
- **Ingestion verwendet `Settings.COLLECTION_PREFIX`**, das `MINDNET_PREFIX` liest
|
||||
- **Resultat**: Daten werden in verschiedene Collections geschrieben/gesucht:
|
||||
- Dev: `mindnet_dev_chunks`, `mindnet_dev_notes`, `mindnet_dev_edges`
|
||||
- Prod: `mindnet_chunks`, `mindnet_notes`, `mindnet_edges`
|
||||
|
||||
### 2. ⚠️ **Mögliche weitere Ursachen**
|
||||
|
||||
#### 2.1 Unterschiedliche Embedding-Modelle
|
||||
- **Prüfen**: `MINDNET_EMBEDDING_MODEL` in Dev vs. Prod
|
||||
- **Auswirkung**: Unterschiedliche Vektoren → unterschiedliche Similarity-Scores
|
||||
|
||||
#### 2.2 Unterschiedliche Vektor-Dimensionen
|
||||
- **Prüfen**: `VECTOR_DIM` in Dev vs. Prod
|
||||
- **Auswirkung**: Dimension-Mismatch → Suche schlägt fehl
|
||||
|
||||
#### 2.3 Unterschiedliche Qdrant-Instanzen
|
||||
- **Prüfen**: `QDRANT_URL` / `QDRANT_HOST` in Dev vs. Prod
|
||||
- **Auswirkung**: Daten liegen in verschiedenen Datenbanken
|
||||
|
||||
#### 2.4 Unterschiedliche Score-Thresholds
|
||||
- **Prüfen**: Filter-Logik oder Mindest-Scores
|
||||
- **Auswirkung**: Ergebnisse werden gefiltert, bevor sie zurückgegeben werden
|
||||
|
||||
## Diagnose-Checkliste
|
||||
|
||||
### ✅ Sofort prüfen:
|
||||
|
||||
1. **Collection-Präfix-Verifikation**:
|
||||
```bash
|
||||
# Dev
|
||||
echo $COLLECTION_PREFIX
|
||||
echo $MINDNET_PREFIX
|
||||
|
||||
# Prod
|
||||
echo $COLLECTION_PREFIX
|
||||
echo $MINDNET_PREFIX
|
||||
```
|
||||
|
||||
2. **Qdrant Collections prüfen**:
|
||||
```python
|
||||
# In beiden Systemen ausführen
|
||||
from app.core.database.qdrant import get_client, QdrantConfig
|
||||
cfg = QdrantConfig.from_env()
|
||||
client = get_client(cfg)
|
||||
print(f"Prefix: {cfg.prefix}")
|
||||
print(f"Collections: {client.get_collections().collections}")
|
||||
```
|
||||
|
||||
3. **Embedding-Modell prüfen**:
|
||||
```bash
|
||||
# Dev
|
||||
echo $MINDNET_EMBEDDING_MODEL
|
||||
echo $VECTOR_DIM
|
||||
|
||||
# Prod
|
||||
echo $MINDNET_EMBEDDING_MODEL
|
||||
echo $VECTOR_DIM
|
||||
```
|
||||
|
||||
4. **Qdrant-Verbindung prüfen**:
|
||||
```bash
|
||||
# Dev
|
||||
echo $QDRANT_URL
|
||||
echo $QDRANT_HOST
|
||||
echo $QDRANT_PORT
|
||||
|
||||
# Prod
|
||||
echo $QDRANT_URL
|
||||
echo $QDRANT_HOST
|
||||
echo $QDRANT_PORT
|
||||
```
|
||||
|
||||
## Lösungsvorschläge
|
||||
|
||||
### Option 1: Harmonisierung der Umgebungsvariablen (Empfohlen)
|
||||
|
||||
**Ziel**: Eine einzige Umgebungsvariable für den Collection-Präfix verwenden.
|
||||
|
||||
**Änderungen**:
|
||||
1. **`app/core/database/qdrant.py`**:
|
||||
```python
|
||||
prefix = os.getenv("COLLECTION_PREFIX") or os.getenv("MINDNET_PREFIX") or "mindnet"
|
||||
```
|
||||
- Unterstützt beide Variablen (Abwärtskompatibilität)
|
||||
- `COLLECTION_PREFIX` hat Priorität
|
||||
|
||||
2. **`app/config.py`**:
|
||||
```python
|
||||
COLLECTION_PREFIX: str = os.getenv("COLLECTION_PREFIX") or os.getenv("MINDNET_PREFIX") or "mindnet_dev"
|
||||
```
|
||||
- Unterstützt beide Variablen
|
||||
- `COLLECTION_PREFIX` hat Priorität
|
||||
|
||||
3. **Dokumentation**: Klarstellen, dass `COLLECTION_PREFIX` die primäre Variable ist
|
||||
|
||||
### Option 2: Explizite Konfiguration in .env
|
||||
|
||||
**Ziel**: Beide Systeme verwenden explizit gesetzte `COLLECTION_PREFIX` Werte.
|
||||
|
||||
**Dev `.env`**:
|
||||
```env
|
||||
COLLECTION_PREFIX=mindnet_dev
|
||||
```
|
||||
|
||||
**Prod `.env`**:
|
||||
```env
|
||||
COLLECTION_PREFIX=mindnet
|
||||
```
|
||||
|
||||
### Option 3: Daten-Migration
|
||||
|
||||
**Ziel**: Daten von einer Collection in die andere migrieren.
|
||||
|
||||
**Vorgehen**:
|
||||
1. Identifizieren, welche Collection die "richtigen" Daten enthält
|
||||
2. Daten von Dev nach Prod migrieren (oder umgekehrt)
|
||||
3. Collection-Präfix harmonisieren
|
||||
|
||||
## Sofortmaßnahmen
|
||||
|
||||
1. ✅ **Prüfen**: Welche Collections existieren in beiden Systemen?
|
||||
2. ✅ **Prüfen**: Welche Umgebungsvariablen sind gesetzt?
|
||||
3. ✅ **Prüfen**: Welche Collection enthält die Daten?
|
||||
4. ✅ **Fix**: Collection-Präfix-Konfiguration harmonisieren
|
||||
5. ✅ **Test**: Retrieval in beiden Systemen verifizieren
|
||||
|
||||
## Erwartetes Ergebnis nach Fix
|
||||
|
||||
- ✅ Beide Systeme verwenden dieselbe Collection-Präfix-Logik
|
||||
- ✅ Retrieval findet Daten in beiden Systemen
|
||||
- ✅ Konsistente Konfiguration zwischen Ingestion und Retrieval
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
doc_type: operations_manual
|
||||
audience: admin, devops
|
||||
scope: deployment, maintenance, backup, edge_registry, moe, lazy_prompts, agentic_validation
|
||||
scope: deployment, maintenance, backup, edge_registry
|
||||
status: active
|
||||
version: 4.5.8
|
||||
context: "Installationsanleitung, Systemd-Units und Wartungsprozesse für Mindnet v4.5.8 inklusive WP-25a Mixture of Experts (MoE), WP-25b Lazy-Prompt-Orchestration und WP-24c Phase 3 Agentic Edge Validation Konfiguration."
|
||||
version: 2.7.0
|
||||
context: "Installationsanleitung, Systemd-Units und Wartungsprozesse für Mindnet v2.7."
|
||||
---
|
||||
|
||||
# Admin Operations Guide
|
||||
|
|
@ -46,7 +46,7 @@ Um Abstürze der Vektordatenbank bei einer hohen Anzahl an Collections (z. B. du
|
|||
Hintergrund: Qdrant öffnet für jedes Segment einer Collection mehrere Dateien. Ohne diese Erhöhung führt das Standard-Linux-Limit (1024) zum Absturz mit dem Fehler os error 24 (Too many open files).
|
||||
|
||||
### 1.3 Ollama (Modelle)
|
||||
**Wichtig:** Seit v2.4 ist `nomic-embed-text` Pflicht für Embeddings. Seit WP-25a wird die Modell-Konfiguration zentral über `llm_profiles.yaml` gesteuert.
|
||||
**Wichtig:** Seit v2.4 ist `nomic-embed-text` Pflicht für Embeddings.
|
||||
|
||||
```bash
|
||||
# Modelle laden
|
||||
|
|
@ -57,14 +57,6 @@ ollama pull nomic-embed-text
|
|||
curl http://localhost:11434/api/generate -d '{"model": "phi3:mini", "prompt":"Hi"}'
|
||||
```
|
||||
|
||||
**WP-25a: LLM-Profil-Konfiguration**
|
||||
Die LLM-Steuerung erfolgt nun primär über `config/llm_profiles.yaml` statt ENV-Variablen:
|
||||
* **Zentrale Registry:** Alle Experten-Profile (Synthese, Validierung, Kompression) sind in einer Datei definiert
|
||||
* **Fallback-Kaskade:** Automatische Resilienz bei Provider-Fehlern
|
||||
* **ENV-Variablen:** `MINDNET_LLM_PROVIDER`, `MINDNET_LLM_MODEL` etc. dienen nur noch als Fallback
|
||||
|
||||
Siehe [Konfigurations-Referenz](../03_Technical_References/03_tech_configuration.md#6-llm-profile-registry-llm_profilesyaml-v130) für Details.
|
||||
|
||||
---
|
||||
|
||||
## 2. Deployment (Systemd Services)
|
||||
|
|
@ -139,149 +131,30 @@ Administratoren sollten regelmäßig das Log für unbekannte Kanten-Typen prüfe
|
|||
|
||||
### 3.3 Troubleshooting Guide
|
||||
|
||||
Dieser Abschnitt hilft bei häufigen Problemen und deren Lösung.
|
||||
|
||||
#### Allgemeine Diagnose-Schritte
|
||||
|
||||
Bevor du spezifische Fehler behebst, führe diese Checks durch:
|
||||
|
||||
1. **Service-Status prüfen:**
|
||||
```bash
|
||||
systemctl status mindnet-prod
|
||||
systemctl status mindnet-ui-prod
|
||||
docker ps | grep qdrant
|
||||
```
|
||||
|
||||
2. **Logs analysieren:**
|
||||
```bash
|
||||
journalctl -u mindnet-prod -n 50 --no-pager
|
||||
journalctl -u mindnet-ui-prod -n 50 --no-pager
|
||||
docker logs qdrant --tail 50
|
||||
```
|
||||
|
||||
3. **API-Verfügbarkeit testen:**
|
||||
```bash
|
||||
curl http://localhost:8001/health
|
||||
curl http://localhost:8001/query -X POST -H "Content-Type: application/json" -d '{"query": "test", "top_k": 1}'
|
||||
```
|
||||
|
||||
#### Häufige Fehler & Lösungen
|
||||
|
||||
**Fehler: "Registry Initialization Failure" (Neu in v2.7)**
|
||||
* **Symptom:** API startet, aber Kanten werden nicht gewichtet oder Fehlermeldung im Log.
|
||||
* **Diagnose:** Prüfe die Logs auf `EdgeRegistry`-Fehler.
|
||||
* **Lösung:**
|
||||
1. Prüfe `MINDNET_VOCAB_PATH` in der `.env`. Der Pfad muss absolut sein.
|
||||
2. Stelle sicher, dass die Datei `01_edge_vocabulary.md` existiert und eine gültige Markdown-Tabelle enthält.
|
||||
3. Prüfe Dateiberechtigungen: `ls -l $MINDNET_VOCAB_PATH`
|
||||
* **Lösung:** Prüfen Sie `MINDNET_VOCAB_PATH` in der `.env`. Der Pfad muss absolut sein und auf eine existierende Markdown-Tabelle zeigen.
|
||||
|
||||
**Fehler: "ModuleNotFoundError: No module named 'st_cytoscape'"**
|
||||
* **Ursache:** Alte Dependencies oder falsches Paket installiert.
|
||||
* **Lösung:** Environment aktualisieren.
|
||||
* Ursache: Alte Dependencies oder falsches Paket installiert.
|
||||
* Lösung: Environment aktualisieren.
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
pip uninstall streamlit-cytoscapejs
|
||||
pip install st-cytoscape
|
||||
pip install -r requirements.txt # Vollständige Synchronisation
|
||||
```
|
||||
|
||||
**Fehler: "Vector dimension error: expected 768, got 384"**
|
||||
* **Ursache:** Alte DB (v2.2), neues Modell (v2.4) oder falsches Embedding-Modell.
|
||||
* **Diagnose:** Prüfe die Collection-Konfiguration in Qdrant.
|
||||
* **Lösung:** **Full Reset** (siehe Kap. 4.2) oder Collection neu erstellen:
|
||||
```bash
|
||||
python3 -m scripts.reset_qdrant --mode wipe --prefix mindnet --yes
|
||||
python3 -m scripts.import_markdown --vault ./vault --prefix mindnet --apply --force
|
||||
```
|
||||
* Ursache: Alte DB (v2.2), neues Modell (v2.4).
|
||||
* Lösung: **Full Reset** (siehe Kap. 4.2).
|
||||
|
||||
**Fehler: Import sehr langsam**
|
||||
* **Ursache:** Smart Edges sind aktiv und analysieren jeden Chunk mit LLM-Calls.
|
||||
* **Diagnose:** Prüfe `MINDNET_LLM_BACKGROUND_LIMIT` und LLM-Provider-Status.
|
||||
* **Lösung:**
|
||||
1. Erhöhe `MINDNET_LLM_BACKGROUND_LIMIT` in `.env` (Standard: 2)
|
||||
2. Oder deaktiviere Smart Edges temporär in `types.yaml` für bestimmte Typen
|
||||
3. Prüfe, ob Ollama/Cloud-Provider erreichbar ist
|
||||
* Ursache: Smart Edges sind aktiv und analysieren jeden Chunk.
|
||||
* Lösung: `MINDNET_LLM_BACKGROUND_LIMIT` prüfen oder Feature in `types.yaml` deaktivieren.
|
||||
|
||||
**Fehler: UI "Read timed out"**
|
||||
* **Ursache:** Backend braucht für Smart Edges länger als das Timeout-Limit.
|
||||
* **Diagnose:** Prüfe Backend-Logs auf langsame LLM-Calls.
|
||||
* **Lösung:**
|
||||
1. Erhöhe `MINDNET_API_TIMEOUT=300.0` in `.env` (oder im Systemd Service)
|
||||
2. Prüfe `MINDNET_LLM_TIMEOUT` für einzelne LLM-Requests
|
||||
3. Erwäge, Smart Edges für große Imports zu deaktivieren
|
||||
|
||||
**Fehler: "Qdrant connection refused"**
|
||||
* **Ursache:** Qdrant-Container läuft nicht oder falsche URL.
|
||||
* **Lösung:**
|
||||
```bash
|
||||
docker ps | grep qdrant # Prüfe Container-Status
|
||||
docker start qdrant # Starte Container falls gestoppt
|
||||
docker logs qdrant # Prüfe Container-Logs
|
||||
# Prüfe QDRANT_URL in .env
|
||||
```
|
||||
|
||||
**Fehler: "Ollama model not found"**
|
||||
* **Ursache:** Modell nicht geladen oder falscher Modellname.
|
||||
* **Lösung:**
|
||||
```bash
|
||||
ollama list # Zeige geladene Modelle
|
||||
ollama pull phi3:mini # Lade fehlendes Modell
|
||||
ollama pull nomic-embed-text
|
||||
# Prüfe MINDNET_LLM_MODEL und MINDNET_EMBEDDING_MODEL in .env
|
||||
```
|
||||
|
||||
**Fehler: "Too many open files" (Qdrant)**
|
||||
* **Ursache:** System-Limit für offene Dateien zu niedrig (besonders bei vielen Collections).
|
||||
* **Lösung:** Erhöhe `ulimits` im Docker-Compose (siehe Kap. 1.2) oder systemweit:
|
||||
```bash
|
||||
# Temporär
|
||||
ulimit -n 65535
|
||||
# Permanently: /etc/security/limits.conf
|
||||
```
|
||||
|
||||
**Fehler: "Unknown edge type" in Logs**
|
||||
* **Ursache:** Neue Kanten-Typen im Vault, die nicht in `edge_vocabulary.md` definiert sind.
|
||||
* **Diagnose:** Prüfe `data/logs/unknown_edges.jsonl`.
|
||||
* **Lösung:**
|
||||
1. Füge fehlende Typen als Aliase in `01_edge_vocabulary.md` hinzu
|
||||
2. Oder verwende kanonische Typen aus der Registry
|
||||
|
||||
**Fehler: "Phase 3 Validierung schlägt fehl" (WP-24c v4.5.8)**
|
||||
* **Symptom:** Links in `### Unzugeordnete Kanten` werden nicht validiert oder abgelehnt.
|
||||
* **Diagnose:** Prüfe Logs auf `🚀 [PHASE 3]` und `🚫 [PHASE 3] REJECTED`.
|
||||
* **Lösung:**
|
||||
1. Prüfe `MINDNET_LLM_VALIDATION_HEADERS` in `.env` (Standard: `Unzugeordnete Kanten,Edge Pool,Candidates`)
|
||||
2. Prüfe `MINDNET_LLM_VALIDATION_HEADER_LEVEL` (Standard: `3` für `###`)
|
||||
3. Prüfe `llm_profiles.yaml` - `ingest_validator` Profil muss existieren
|
||||
4. Prüfe LLM-Verfügbarkeit (Ollama/OpenRouter)
|
||||
5. **Hinweis:** Transiente Fehler (Netzwerk) erlauben die Kante, permanente Fehler lehnen sie ab
|
||||
|
||||
**Fehler: "Note-Scope Links werden nicht erkannt" (WP-24c v4.2.0)**
|
||||
* **Symptom:** Links in `## Smart Edges` Zonen werden nicht als Note-Scope behandelt.
|
||||
* **Diagnose:** Prüfe Logs auf Note-Scope Extraktion.
|
||||
* **Lösung:**
|
||||
1. Prüfe `MINDNET_NOTE_SCOPE_ZONE_HEADERS` in `.env` (Standard: `Smart Edges,Relationen,Global Links`)
|
||||
2. Prüfe `MINDNET_NOTE_SCOPE_HEADER_LEVEL` (Standard: `2` für `##`)
|
||||
3. Header-Namen müssen exakt (case-insensitive) übereinstimmen
|
||||
|
||||
#### Performance-Optimierung
|
||||
|
||||
**Problem: Langsame Chat-Antworten**
|
||||
* Prüfe LLM-Provider (Cloud vs. lokal)
|
||||
* Reduziere `top_k` in Query-Requests
|
||||
* Prüfe Qdrant-Performance (Anzahl Collections, Index-Größe)
|
||||
|
||||
**Problem: Hoher Speicherverbrauch**
|
||||
* Reduziere `MINDNET_LLM_BACKGROUND_LIMIT`
|
||||
* Prüfe Qdrant-Speicherverbrauch: `docker stats qdrant`
|
||||
* Erwäge, alte Collections zu archivieren
|
||||
|
||||
#### Weitere Hilfe
|
||||
|
||||
Für detaillierte Informationen zu:
|
||||
- **Server-Betrieb:** Siehe [Server Operations Manual](04_server_operation_manual.md)
|
||||
- **Entwicklung:** Siehe [Developer Guide](../05_Development/05_developer_guide.md#10-troubleshooting--one-liners)
|
||||
- **Konfiguration:** Siehe [Configuration Reference](../03_Technical_References/03_tech_configuration.md)
|
||||
* Ursache: Backend braucht für Smart Edges länger als 60s.
|
||||
* Lösung: `MINDNET_API_TIMEOUT=300.0` in `.env` setzen (oder im Systemd Service).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -306,10 +179,3 @@ python3 -m scripts.reset_qdrant --mode wipe --prefix "mindnet" --yes
|
|||
# 2. Neu importieren (Force Hash recalculation)
|
||||
python3 -m scripts.import_markdown --vault ./vault --prefix "mindnet" --apply --force
|
||||
```
|
||||
|
||||
**Wichtig (v2.9.1 Migration):**
|
||||
Nach dem Update auf v2.9.1 (Section-basierte Links, Multigraph-Support) ist ein vollständiger Re-Import erforderlich, um "Phantom-Knoten" zu beheben und die neue Edge-Struktur zu konsolidieren:
|
||||
```bash
|
||||
python3 -m scripts.import_markdown --vault ./vault --prefix "mindnet" --apply --force
|
||||
```
|
||||
Dies stellt sicher, dass alle bestehenden Links korrekt in `target_id` und `target_section` aufgeteilt werden.
|
||||
|
|
@ -1,474 +0,0 @@
|
|||
---
|
||||
doc_type: operations_manual
|
||||
audience: devops, deployment_engineer
|
||||
scope: deployment, ci_cd, rollout, versioning
|
||||
status: active
|
||||
version: 2.9.1
|
||||
context: "Vollständiger Deployment-Guide für Mindnet: CI/CD, Rollout-Strategien, Versionierung und Rollback."
|
||||
---
|
||||
|
||||
# Deployment Guide
|
||||
|
||||
Dieses Dokument beschreibt die Deployment-Prozesse, CI/CD-Pipelines und Rollout-Strategien für Mindnet.
|
||||
|
||||
## 1. Deployment-Architektur
|
||||
|
||||
Mindnet läuft in einer **Multi-Environment-Architektur**:
|
||||
|
||||
| Environment | Ports | Zweck | User |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Production** | 8001 (API), 8501 (UI) | Live-System | `llmadmin` |
|
||||
| **Development** | 8002 (API), 8502 (UI) | Test & Entwicklung | `llmadmin` |
|
||||
|
||||
**Wichtig:** Beide Environments teilen sich die gleiche Qdrant-Instanz, nutzen aber unterschiedliche Collection-Prefixes.
|
||||
|
||||
---
|
||||
|
||||
## 2. Deployment-Methoden
|
||||
|
||||
### 2.1 Automatisches Deployment (CI/CD)
|
||||
|
||||
**Tool:** Gitea Actions (`.gitea/workflows/deploy.yml`)
|
||||
|
||||
**Trigger:** Push auf `main` Branch
|
||||
|
||||
**Prozess:**
|
||||
1. **Checkout:** Code wird ausgecheckt
|
||||
2. **Stop API:** Graceful Stop des API-Services
|
||||
3. **Deploy:** Rsync der whitelisted Verzeichnisse
|
||||
4. **Dependencies:** Python venv & requirements aktualisieren
|
||||
5. **Restart:** API-Service neu starten
|
||||
|
||||
**Deployierte Verzeichnisse:**
|
||||
- `app/` - Backend-Code
|
||||
- `scripts/` - Admin-Tools
|
||||
- `config/` - Konfigurationsdateien
|
||||
- `tests/` - Test-Suite
|
||||
- `requirements.txt` - Dependencies
|
||||
|
||||
**Ausgeschlossen:**
|
||||
- `.env*` - Umgebungsvariablen (bleiben auf Server)
|
||||
- `.venv` - Virtuelle Umgebung (wird neu erstellt)
|
||||
- `vault/` - Content (bleibt auf Server)
|
||||
|
||||
### 2.2 Manuelles Deployment
|
||||
|
||||
**Für:** Hotfixes, manuelle Rollouts, Debugging
|
||||
|
||||
**Prozess:**
|
||||
```bash
|
||||
# 1. Auf Server einloggen
|
||||
ssh llmadmin@llm-node
|
||||
|
||||
# 2. In Produktions-Verzeichnis wechseln
|
||||
cd ~/mindnet
|
||||
|
||||
# 3. Code aktualisieren
|
||||
git fetch origin
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# 4. Dependencies aktualisieren
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 5. Services neu starten
|
||||
sudo systemctl restart mindnet-prod
|
||||
sudo systemctl restart mindnet-ui-prod
|
||||
|
||||
# 6. Status prüfen
|
||||
sudo systemctl status mindnet-prod
|
||||
sudo systemctl status mindnet-ui-prod
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Systemd Services
|
||||
|
||||
### 3.1 Backend Service (API)
|
||||
|
||||
**Datei:** `/etc/systemd/system/mindnet-prod.service`
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Mindnet API Prod (8001)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=llmadmin
|
||||
Group=llmadmin
|
||||
WorkingDirectory=/home/llmadmin/mindnet
|
||||
ExecStart=/home/llmadmin/mindnet/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8001 --env-file .env
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
Environment="MINDNET_VOCAB_PATH=/home/llmadmin/mindnet/vault/_system/dictionary/edge_vocabulary.md"
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
**Wichtig:**
|
||||
- `--env-file .env` lädt Umgebungsvariablen
|
||||
- `MINDNET_VOCAB_PATH` muss absolut sein (für Edge Registry)
|
||||
|
||||
### 3.2 Frontend Service (UI)
|
||||
|
||||
**Datei:** `/etc/systemd/system/mindnet-ui-prod.service`
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Mindnet UI Prod (8501)
|
||||
After=mindnet-prod.service
|
||||
|
||||
[Service]
|
||||
User=llmadmin
|
||||
Group=llmadmin
|
||||
WorkingDirectory=/home/llmadmin/mindnet
|
||||
Environment="MINDNET_API_URL=http://localhost:8001"
|
||||
Environment="MINDNET_API_TIMEOUT=300"
|
||||
Environment="STREAMLIT_SERVER_PORT=8501"
|
||||
Environment="STREAMLIT_SERVER_ADDRESS=0.0.0.0"
|
||||
Environment="STREAMLIT_SERVER_HEADLESS=true"
|
||||
ExecStart=/home/llmadmin/mindnet/.venv/bin/streamlit run app/frontend/ui.py
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
### 3.3 Service-Management
|
||||
|
||||
**Befehle:**
|
||||
```bash
|
||||
# Status prüfen
|
||||
sudo systemctl status mindnet-prod
|
||||
sudo systemctl status mindnet-ui-prod
|
||||
|
||||
# Starten
|
||||
sudo systemctl start mindnet-prod
|
||||
sudo systemctl start mindnet-ui-prod
|
||||
|
||||
# Stoppen
|
||||
sudo systemctl stop mindnet-prod
|
||||
sudo systemctl stop mindnet-ui-prod
|
||||
|
||||
# Neustart
|
||||
sudo systemctl restart mindnet-prod
|
||||
sudo systemctl restart mindnet-ui-prod
|
||||
|
||||
# Logs anzeigen
|
||||
sudo journalctl -u mindnet-prod -f
|
||||
sudo journalctl -u mindnet-ui-prod -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Rollout-Strategien
|
||||
|
||||
### 4.1 Blue-Green Deployment (Empfohlen)
|
||||
|
||||
**Konzept:** Zwei identische Environments, Switch zwischen ihnen.
|
||||
|
||||
**Aktuell:** Prod/Dev als Blue-Green Setup
|
||||
|
||||
**Vorgehen:**
|
||||
1. Deploy auf Dev (Port 8002/8502)
|
||||
2. Tests auf Dev durchführen
|
||||
3. Wenn erfolgreich: Deploy auf Prod (Port 8001/8501)
|
||||
4. Switch erfolgt durch Service-Restart
|
||||
|
||||
**Vorteile:**
|
||||
- Schneller Rollback (alte Version läuft noch)
|
||||
- Keine Downtime
|
||||
- Test vor Produktion
|
||||
|
||||
### 4.2 Canary Deployment (Zukünftig)
|
||||
|
||||
**Konzept:** Schrittweise Rollout an einen Teil der Nutzer.
|
||||
|
||||
**Umsetzung:**
|
||||
- Load Balancer mit Traffic-Splitting
|
||||
- Monitoring der Fehlerrate
|
||||
- Automatischer Rollback bei Fehlern
|
||||
|
||||
**Status:** Noch nicht implementiert (Single-User-Szenario)
|
||||
|
||||
---
|
||||
|
||||
## 5. Versionierung & Releases
|
||||
|
||||
### 5.1 Version-Schema
|
||||
|
||||
**Format:** `v2.9.1`
|
||||
|
||||
- **Major (2):** Breaking Changes
|
||||
- **Minor (9):** Neue Features, Backward Compatible
|
||||
- **Patch (1):** Bugfixes, kleine Verbesserungen
|
||||
|
||||
### 5.2 Release-Prozess
|
||||
|
||||
**1. Feature-Entwicklung:**
|
||||
```bash
|
||||
git checkout -b feature/neue-funktion
|
||||
# ... Entwicklung ...
|
||||
git push origin feature/neue-funktion
|
||||
```
|
||||
|
||||
**2. Testing:**
|
||||
- Unit Tests
|
||||
- Integration Tests
|
||||
- Smoke Tests auf Dev
|
||||
|
||||
**3. Merge:**
|
||||
```bash
|
||||
# Pull Request in Gitea
|
||||
# Review & Merge nach main
|
||||
```
|
||||
|
||||
**4. Deployment:**
|
||||
- Automatisch via CI/CD (bei Push auf main)
|
||||
- Oder manuell (siehe 2.2)
|
||||
|
||||
**5. Tagging (Optional):**
|
||||
```bash
|
||||
git tag -a v2.9.2 -m "Release v2.9.2: Neue Funktion X"
|
||||
git push origin v2.9.2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Rollback-Strategien
|
||||
|
||||
### 6.1 Code-Rollback
|
||||
|
||||
**Methode 1: Git Revert**
|
||||
```bash
|
||||
cd ~/mindnet
|
||||
git log --oneline -10 # Finde letzten guten Commit
|
||||
git checkout <commit-hash>
|
||||
sudo systemctl restart mindnet-prod
|
||||
```
|
||||
|
||||
**Methode 2: Git Reset (Vorsicht!)**
|
||||
```bash
|
||||
cd ~/mindnet
|
||||
git reset --hard <commit-hash>
|
||||
sudo systemctl restart mindnet-prod
|
||||
```
|
||||
|
||||
### 6.2 Datenbank-Rollback
|
||||
|
||||
**Problem:** Code-Rollback hilft nicht bei Schema-Änderungen.
|
||||
|
||||
**Lösung:**
|
||||
- **Qdrant-Snapshots:** Regelmäßige Backups (siehe [Server Operations Manual](04_server_operation_manual.md))
|
||||
- **Collection-Versionierung:** Separate Collections pro Version (nicht empfohlen)
|
||||
|
||||
**Vorgehen bei Schema-Änderung:**
|
||||
1. Snapshot vor Deployment erstellen
|
||||
2. Deployment durchführen
|
||||
3. Bei Problemen: Snapshot wiederherstellen
|
||||
|
||||
---
|
||||
|
||||
## 7. Pre-Deployment Checkliste
|
||||
|
||||
Vor jedem Deployment sollten folgende Punkte geprüft werden:
|
||||
|
||||
- [ ] **Code-Qualität:**
|
||||
- [ ] Unit Tests bestehen
|
||||
- [ ] Integration Tests bestehen
|
||||
- [ ] Linting bestanden
|
||||
|
||||
- [ ] **Dependencies:**
|
||||
- [ ] `requirements.txt` aktualisiert
|
||||
- [ ] Neue Dependencies dokumentiert
|
||||
- [ ] Breaking Changes in Dependencies geprüft
|
||||
|
||||
- [ ] **Konfiguration:**
|
||||
- [ ] `.env` Variablen dokumentiert (falls neu)
|
||||
- [ ] Config-Dateien (`types.yaml`, etc.) kompatibel
|
||||
- [ ] Edge Registry Pfad korrekt
|
||||
|
||||
- [ ] **Datenbank:**
|
||||
- [ ] Schema-Änderungen dokumentiert
|
||||
- [ ] Migration-Skripte vorhanden (falls nötig)
|
||||
- [ ] Backup erstellt
|
||||
|
||||
- [ ] **Dokumentation:**
|
||||
- [ ] Changelog aktualisiert
|
||||
- [ ] Dokumentation synchronisiert
|
||||
- [ ] Breaking Changes dokumentiert
|
||||
|
||||
---
|
||||
|
||||
## 8. Post-Deployment Validierung
|
||||
|
||||
Nach jedem Deployment sollten folgende Checks durchgeführt werden:
|
||||
|
||||
### 8.1 Service-Status
|
||||
|
||||
```bash
|
||||
# Services laufen
|
||||
sudo systemctl status mindnet-prod
|
||||
sudo systemctl status mindnet-ui-prod
|
||||
|
||||
# Health Check
|
||||
curl http://localhost:8001/healthz
|
||||
```
|
||||
|
||||
### 8.2 Funktionalität
|
||||
|
||||
```bash
|
||||
# API-Test
|
||||
curl -X POST http://localhost:8001/query \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "test", "top_k": 1}'
|
||||
|
||||
# UI-Test (Manuell)
|
||||
# Öffne http://localhost:8501 im Browser
|
||||
```
|
||||
|
||||
### 8.3 Logs prüfen
|
||||
|
||||
```bash
|
||||
# Fehler in Logs
|
||||
sudo journalctl -u mindnet-prod --since "5 minutes ago" | grep -i error
|
||||
|
||||
# Warnings
|
||||
sudo journalctl -u mindnet-prod --since "5 minutes ago" | grep -i warning
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. CI/CD Pipeline Details
|
||||
|
||||
### 9.1 Gitea Actions Workflow
|
||||
|
||||
**Datei:** `.gitea/workflows/deploy.yml`
|
||||
|
||||
**Trigger:**
|
||||
- Push auf `main` Branch
|
||||
- Concurrency: Nur ein Deployment gleichzeitig
|
||||
|
||||
**Schritte:**
|
||||
1. **Checkout:** Code aus Repository
|
||||
2. **Stop API:** Graceful Stop (continue-on-error)
|
||||
3. **Deploy:** Rsync der Verzeichnisse
|
||||
4. **Python Setup:** Venv & Requirements
|
||||
5. **Start API:** Service neu starten
|
||||
|
||||
**Wichtig:**
|
||||
- `.env` wird **nicht** deployed (bleibt auf Server)
|
||||
- Vault wird **nicht** deployed (bleibt auf Server)
|
||||
|
||||
### 9.2 Deployment-Verzeichnisse
|
||||
|
||||
**Whitelist:**
|
||||
```
|
||||
app scripts schemas docker tests config requirements.txt README.md
|
||||
```
|
||||
|
||||
**Excluded:**
|
||||
- `.git/`
|
||||
- `.env*`
|
||||
- `.venv/`
|
||||
- `vault/`
|
||||
- `hf_cache/`
|
||||
|
||||
---
|
||||
|
||||
## 10. Monitoring & Alerting
|
||||
|
||||
### 10.1 Health Checks
|
||||
|
||||
**API Health Endpoint:**
|
||||
```bash
|
||||
curl http://localhost:8001/healthz
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"qdrant": "http://localhost:6333",
|
||||
"prefix": "mindnet"
|
||||
}
|
||||
```
|
||||
|
||||
**Monitoring-Script:**
|
||||
```bash
|
||||
python3 -m scripts.health_check_mindnet --url http://localhost:8001 --strict
|
||||
```
|
||||
|
||||
### 10.2 Log-Monitoring
|
||||
|
||||
**Systemd Journal:**
|
||||
```bash
|
||||
# Live-Logs
|
||||
sudo journalctl -u mindnet-prod -f
|
||||
|
||||
# Letzte 100 Zeilen
|
||||
sudo journalctl -u mindnet-prod -n 100
|
||||
|
||||
# Seit gestern
|
||||
sudo journalctl -u mindnet-prod --since "yesterday"
|
||||
```
|
||||
|
||||
### 10.3 Metriken (Zukünftig)
|
||||
|
||||
**Geplante Metriken:**
|
||||
- Request-Rate
|
||||
- Response-Zeiten
|
||||
- Fehler-Rate
|
||||
- LLM-Call-Dauer
|
||||
- Qdrant-Performance
|
||||
|
||||
**Tools:** Prometheus + Grafana (noch nicht implementiert)
|
||||
|
||||
---
|
||||
|
||||
## 11. Disaster Recovery
|
||||
|
||||
Siehe [Server Operations Manual](04_server_operation_manual.md#5-disaster-recovery-wiederherstellung-two-stage-dr) für detaillierte Disaster-Recovery-Prozeduren.
|
||||
|
||||
**Kurzfassung:**
|
||||
1. **Stage 1:** Basis-Image Restore (Bare Metal)
|
||||
2. **Stage 2:** Daten-Update via Borgmatic
|
||||
3. **Dienste:** Gitea, Qdrant, Ollama spezifisch wiederherstellen
|
||||
|
||||
---
|
||||
|
||||
## 12. Best Practices
|
||||
|
||||
### 12.1 Deployment-Zeiten
|
||||
|
||||
- **Produktion:** Während Wartungsfenstern (nachts, Wochenenden)
|
||||
- **Development:** Jederzeit (ist Test-Umgebung)
|
||||
|
||||
### 12.2 Kommunikation
|
||||
|
||||
- **Breaking Changes:** Vorher ankündigen
|
||||
- **Downtime:** Bei größeren Deployments kommunizieren
|
||||
- **Rollback-Plan:** Immer bereit haben
|
||||
|
||||
### 12.3 Testing
|
||||
|
||||
- **Immer zuerst auf Dev testen**
|
||||
- **Smoke Tests nach Deployment**
|
||||
- **Monitoring für erste Stunden nach Deployment**
|
||||
|
||||
---
|
||||
|
||||
## 13. Weitere Informationen
|
||||
|
||||
- **Admin Operations:** Siehe [Admin Operations Guide](04_admin_operations.md)
|
||||
- **Server Operations:** Siehe [Server Operations Manual](04_server_operation_manual.md)
|
||||
- **Troubleshooting:** Siehe [Admin Operations - Troubleshooting](04_admin_operations.md#33-troubleshooting-guide)
|
||||
|
||||
---
|
||||
|
||||
**Letzte Aktualisierung:** 2025-01-XX
|
||||
**Version:** 2.9.1
|
||||
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
doc_type: developer_guide
|
||||
audience: developer
|
||||
scope: workflow, testing, architecture, modules, modularization, agentic_rag, lazy_prompts, agentic_validation
|
||||
scope: workflow, testing, architecture, modules, modularization
|
||||
status: active
|
||||
version: 4.5.8
|
||||
context: "Umfassender Guide für Entwickler: Modularisierte Architektur (WP-14), Two-Pass Ingestion (WP-15b), WP-25 Agentic Multi-Stream RAG, WP-25a MoE, WP-25b Lazy-Prompt-Orchestration, WP-24c Phase 3 Agentic Edge Validation (v4.5.8), Modul-Interna, Setup und Git-Workflow."
|
||||
version: 2.9.1
|
||||
context: "Umfassender Guide für Entwickler: Modularisierte Architektur (WP-14), Two-Pass Ingestion (WP-15b), Modul-Interna, Setup und Git-Workflow."
|
||||
---
|
||||
|
||||
# Mindnet Developer Guide & Workflow
|
||||
|
|
@ -216,27 +216,11 @@ Das Frontend ist eine Streamlit-App, die sich wie eine Single-Page-Application (
|
|||
|
||||
Das Backend ist das Herzstück. Es stellt die Logik via REST-API bereit.
|
||||
|
||||
**Wichtig:** Seit WP-14 ist die Core-Logik in spezialisierte Pakete unterteilt:
|
||||
|
||||
#### Core-Pakete (Modularisierung WP-14)
|
||||
|
||||
| Paket | Zweck | Wichtige Module |
|
||||
| :--- | :--- | :--- |
|
||||
| **`app/core/chunking/`** | Text-Segmentierung | `chunking_strategies.py` (Sliding/Heading), `chunking_processor.py` (Orchestrierung) |
|
||||
| **`app/core/database/`** | Qdrant-Infrastruktur | `qdrant.py` (Client), `qdrant_points.py` (Point-Mapping) |
|
||||
| **`app/core/graph/`** | Graph-Logik | `graph_subgraph.py` (Expansion), `graph_weights.py` (Scoring) |
|
||||
| **`app/core/ingestion/`** | Import-Pipeline | `ingestion_processor.py` (3-Phasen-Modell: Pre-Scan, Semantic Processing, Phase 3 Agentic Validation), `ingestion_validation.py` (Mistral-safe Parsing, Phase 3 Validierung) |
|
||||
| **`app/core/parser/`** | Markdown-Parsing | `parsing_markdown.py` (Frontmatter/Body), `parsing_scanner.py` (File-Scan) |
|
||||
| **`app/core/retrieval/`** | Suche & Scoring | `retriever.py` (Orchestrator), `retriever_scoring.py` (Mathematik) |
|
||||
| **`app/core/registry.py`** | SSOT & Utilities | Text-Bereinigung, Circular-Import-Fix |
|
||||
|
||||
**Legacy-Bridges:** Die alten Dateien (`app/core/retriever.py`, `app/core/qdrant.py`) existieren noch als Proxy-Adapter für Abwärtskompatibilität, delegieren aber an die neuen Pakete.
|
||||
|
||||
| Layer | Datei | Status | Verantwortung |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Entry** | `app/main.py` | 🟢 **Core** | **Entrypoint.** Initialisiert FastAPI, CORS, und bindet alle Router ein. |
|
||||
| **Config** | `app/config.py` | 🟢 **Core** | **Settings.** Zentrale Konfiguration (Pydantic). Lädt Env-Vars für Qdrant, LLM und Pfade. |
|
||||
| **Router** | `app/routers/chat.py` | 🟢 **API** | **Conversation API (WP-25).** Haupt-Endpunkt für Chat. Hybrid Router mit Intent-Erkennung, Multi-Stream Orchestration und Wissens-Synthese. |
|
||||
| **Router** | `app/routers/chat.py` | 🟢 **API** | **Conversation API.** Haupt-Endpunkt für Chat. Entscheidet zwischen Interview- und RAG-Modus. |
|
||||
| | `app/routers/ingest.py` | 🟢 **API** | **Write API.** Nimmt Markdown entgegen, steuert Ingestion und Discovery-Analyse. |
|
||||
| | `app/routers/query.py` | 🟢 **API** | **Search API.** Klassischer Hybrid-Retriever Endpunkt. |
|
||||
| | `app/routers/graph.py` | 🟢 **API** | **Viz API.** Liefert Knoten/Kanten für Frontend-Graphen (Cytoscape). |
|
||||
|
|
@ -393,54 +377,12 @@ Mindnet lernt nicht durch Training (Fine-Tuning), sondern durch **Konfiguration*
|
|||
edge_defaults: ["blocks"] # Automatische Kante
|
||||
detection_keywords: ["gefahr", "risiko"]
|
||||
```
|
||||
2. **Strategie (`config/decision_engine.yaml` v3.2.2, WP-25/25a):**
|
||||
2. **Strategie (`config/decision_engine.yaml`):**
|
||||
```yaml
|
||||
DECISION:
|
||||
use_streams: ["values_stream", "facts_stream", "risk_stream"] # WP-25: Multi-Stream
|
||||
llm_profile: "synthesis_pro" # WP-25a: MoE-Profil für Synthese
|
||||
inject_types: ["value", "risk"] # Legacy: Fallback für nicht-Stream-Typen
|
||||
inject_types: ["value", "risk"] # <--- "risk" hinzufügen
|
||||
```
|
||||
*Ergebnis (WP-25/25a):* Wenn der Intent `DECISION` erkannt wird, führt das System parallele Abfragen in Values, Facts und Risk Streams aus, komprimiert überlange Streams via `compression_profile` und synthetisiert die Ergebnisse mit dem `synthesis_pro` Profil.
|
||||
|
||||
3. **LLM-Profil (`config/llm_profiles.yaml` v1.3.0, WP-25a):**
|
||||
```yaml
|
||||
synthesis_pro:
|
||||
provider: "openrouter"
|
||||
model: "google/gemini-2.0-flash-exp:free"
|
||||
temperature: 0.7
|
||||
fallback_profile: "synthesis_backup"
|
||||
```
|
||||
*Ergebnis (WP-25a):* Zentrale Steuerung von Provider, Modell und Temperature pro Aufgabe. Automatische Fallback-Kaskade bei Fehlern.
|
||||
|
||||
4. **Prompt-Template (`config/prompts.yaml` v3.2.2, WP-25b):**
|
||||
```yaml
|
||||
decision_synthesis_v1:
|
||||
# Level 1: Modell-spezifisch (höchste Priorität)
|
||||
"google/gemini-2.0-flash-exp:free": |
|
||||
WERTE & PRINZIPIEN (Identität):
|
||||
{values_stream}
|
||||
...
|
||||
|
||||
# Level 2: Provider-Fallback
|
||||
openrouter: |
|
||||
WERTE & PRINZIPIEN (Identität):
|
||||
{values_stream}
|
||||
...
|
||||
|
||||
# Level 3: Global Default
|
||||
default: |
|
||||
Synthetisiere die folgenden Informationen für: {query}
|
||||
...
|
||||
```
|
||||
*Ergebnis (WP-25b):* Hierarchische Prompt-Resolution mit Lazy-Loading. Prompts werden erst zur Laufzeit geladen, basierend auf aktivem Modell. Maximale Resilienz bei Modell-Fallbacks.
|
||||
|
||||
5. **Phase 3 Validierung (WP-24c v4.5.8):** Kanten mit `candidate:` Präfix werden automatisch in Phase 3 validiert:
|
||||
* **Trigger:** Kanten in Header-Zonen (konfiguriert via `MINDNET_LLM_VALIDATION_HEADERS`) erhalten `candidate:` Präfix
|
||||
* **Validierung:** Nutzt `ingest_validator` Profil (Temperature 0.0) für deterministische YES/NO Entscheidungen
|
||||
* **Kontext-Optimierung:** Note-Scope nutzt `note_summary`, Chunk-Scope nutzt spezifischen Chunk-Text
|
||||
* **Erfolg:** Entfernt `candidate:` Präfix, Kante wird persistiert
|
||||
* **Ablehnung:** Kante wird zu `rejected_edges` hinzugefügt und **nicht** in DB geschrieben
|
||||
* **Logging:** `🚀 [PHASE 3]` für Start, `✅ [PHASE 3] VERIFIED` für Erfolg, `🚫 [PHASE 3] REJECTED` für Ablehnung
|
||||
*Ergebnis:* Wenn der Intent `DECISION` erkannt wird, sucht das System nun auch aktiv nach Risiken.
|
||||
|
||||
### Workflow B: Graph-Farben ändern
|
||||
1. Öffne `app/frontend/ui_config.py`.
|
||||
|
|
|
|||
|
|
@ -1,412 +0,0 @@
|
|||
---
|
||||
doc_type: developer_guide
|
||||
audience: developer, tester
|
||||
scope: testing, quality_assurance, test_strategies, agentic_validation
|
||||
status: active
|
||||
version: 4.5.8
|
||||
context: "Umfassender Test-Guide für Mindnet: Test-Strategien, Test-Frameworks, Test-Daten und Best Practices inklusive WP-25 Multi-Stream RAG und WP-24c Phase 3 Agentic Edge Validation."
|
||||
---
|
||||
|
||||
# Testing Guide
|
||||
|
||||
Dieses Dokument beschreibt die Test-Strategien, Test-Frameworks und Best Practices für die Qualitätssicherung von Mindnet.
|
||||
|
||||
## 1. Test-Strategie & Ebenen
|
||||
|
||||
Mindnet nutzt eine **dreistufige Test-Pyramide**:
|
||||
|
||||
```
|
||||
/\
|
||||
/E2E\ ← Wenige, langsame, teure Tests
|
||||
/------\
|
||||
/Integration\ ← Mittlere Anzahl, mittlere Geschwindigkeit
|
||||
/------------\
|
||||
/ Unit Tests \ ← Viele, schnelle, isolierte Tests
|
||||
/----------------\
|
||||
```
|
||||
|
||||
### 1.1 Unit Tests (Pytest)
|
||||
|
||||
**Zweck:** Isolierte Logik-Tests ohne externe Abhängigkeiten.
|
||||
|
||||
**Framework:** `pytest`
|
||||
|
||||
**Beispiele:**
|
||||
- `tests/test_retriever_basic.py` - Scoring-Logik
|
||||
- `tests/test_chunking.py` - Chunking-Strategien
|
||||
- `tests/test_edges_all.py` - Edge-Logik
|
||||
- `tests/test_type_registry.py` - Registry-Funktionen
|
||||
|
||||
**Ausführung:**
|
||||
```bash
|
||||
# Einzelner Test
|
||||
pytest tests/test_retriever_basic.py -v
|
||||
|
||||
# Alle Unit Tests
|
||||
pytest tests/ -k "test_" --ignore=tests/test_*_smoke.py
|
||||
|
||||
# Mit Coverage
|
||||
pytest tests/ --cov=app --cov-report=html
|
||||
```
|
||||
|
||||
**Best Practices:**
|
||||
- Tests sollten isoliert sein (keine Abhängigkeiten zu Qdrant/Ollama)
|
||||
- Mock externe Services (LLM, Qdrant)
|
||||
- Schnelle Ausführung (< 1 Sekunde pro Test)
|
||||
|
||||
### 1.2 Integration Tests
|
||||
|
||||
**Zweck:** Prüfen den Datenfluss von Markdown bis Qdrant.
|
||||
|
||||
**Tools:**
|
||||
- `scripts/payload_dryrun.py` - JSON-Schema-Konformität
|
||||
- `scripts/edges_full_check.py` - Graph-Integrität
|
||||
- `scripts/make_test_vault.py` - Test-Daten-Generierung
|
||||
|
||||
**Ausführung:**
|
||||
```bash
|
||||
# Payload-Validierung
|
||||
python3 -m scripts.payload_dryrun --vault ./test_vault --with-edges
|
||||
|
||||
# Graph-Integrität
|
||||
python3 -m scripts.edges_full_check
|
||||
|
||||
# Test-Vault erstellen
|
||||
python3 -m scripts.make_test_vault --out ./test_vault --force
|
||||
```
|
||||
|
||||
**Was wird geprüft:**
|
||||
- Frontmatter-Parsing
|
||||
- Chunk-Generierung
|
||||
- Edge-Erstellung
|
||||
- Payload-Schema-Konformität
|
||||
- Graph-Invarianten (keine Dangling Edges)
|
||||
|
||||
### 1.3 E2E / Smoke Tests
|
||||
|
||||
**Zweck:** Prüfen das laufende System gegen echte Infrastruktur (Qdrant, Ollama).
|
||||
|
||||
**Framework:** Python `unittest` + Shell-Skripte
|
||||
|
||||
**Beispiele:**
|
||||
- `tests/test_wp06_decision.py` - Decision Engine
|
||||
- `tests/test_feedback_smoke.py` - Feedback-Loop
|
||||
- `tests/test_dialog_full_flow.py` - Vollständiger Dialog-Flow
|
||||
- `tests/run_e2e_roundtrip.sh` - Kompletter Roundtrip (Import → Export)
|
||||
|
||||
**Ausführung:**
|
||||
```bash
|
||||
# Decision Engine Test
|
||||
python tests/test_wp06_decision.py -p 8002 -e DECISION -q "Soll ich X tun?"
|
||||
|
||||
# Feedback Test
|
||||
python tests/test_feedback_smoke.py --url http://localhost:8002/query
|
||||
|
||||
# E2E Roundtrip
|
||||
./tests/run_e2e_roundtrip.sh --vault ./test_vault --prefix mindnet_test
|
||||
```
|
||||
|
||||
**Voraussetzungen:**
|
||||
- Qdrant läuft auf `localhost:6333`
|
||||
- Ollama läuft auf `localhost:11434`
|
||||
- API läuft auf Port 8002 (Dev)
|
||||
|
||||
---
|
||||
|
||||
## 2. Test-Daten & Vaults
|
||||
|
||||
### 2.1 Test-Vault erstellen
|
||||
|
||||
**Tool:** `scripts/make_test_vault.py`
|
||||
|
||||
**Zweck:** Erstellt einen minimalen, nachvollziehbaren Test-Vault.
|
||||
|
||||
**Inhalt:**
|
||||
- Verschiedene Note-Typen (`concept`, `experience`, `project`)
|
||||
- Verschiedene Edge-Szenarien (explicit, implicit, missing links)
|
||||
- Externe Links für Edge-Tests
|
||||
|
||||
**Verwendung:**
|
||||
```bash
|
||||
# Standard
|
||||
python3 -m scripts.make_test_vault --out ./test_vault
|
||||
|
||||
# Mit fehlenden Links (für Link-Auflösungs-Tests)
|
||||
python3 -m scripts.make_test_vault --out ./test_vault --with-missing
|
||||
|
||||
# Überschreiben bestehenden Vault
|
||||
python3 -m scripts.make_test_vault --out ./test_vault --force
|
||||
```
|
||||
|
||||
### 2.2 Test-Collection Prefix
|
||||
|
||||
**Wichtig:** Nutze separate Prefixes für Tests, um Produktionsdaten nicht zu beeinflussen.
|
||||
|
||||
```bash
|
||||
export COLLECTION_PREFIX="mindnet_test"
|
||||
python3 -m scripts.import_markdown --vault ./test_vault --prefix mindnet_test --apply
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Test-Frameworks & Tools
|
||||
|
||||
### 3.1 Pytest (Unit Tests)
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
pip install pytest pytest-asyncio pytest-cov
|
||||
```
|
||||
|
||||
**Konfiguration:** `pytest.ini` oder `pyproject.toml`
|
||||
|
||||
**Async Tests:**
|
||||
```python
|
||||
import pytest
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_function():
|
||||
result = await async_function()
|
||||
assert result == expected
|
||||
```
|
||||
|
||||
### 3.2 Unittest (E2E Tests)
|
||||
|
||||
**Framework:** Python `unittest`
|
||||
|
||||
**Beispiel:**
|
||||
```python
|
||||
import unittest
|
||||
from app.routers.ingest import save_note, SaveRequest
|
||||
|
||||
class TestIngest(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_save_note(self):
|
||||
req = SaveRequest(markdown_content="...", filename="test.md")
|
||||
response = await save_note(req)
|
||||
self.assertEqual(response.status, "queued")
|
||||
```
|
||||
|
||||
### 3.3 Shell-Skripte (E2E Roundtrip)
|
||||
|
||||
**Tool:** `tests/run_e2e_roundtrip.sh`
|
||||
|
||||
**Zweck:** Vollständiger Test-Zyklus (Import → Export → Vergleich)
|
||||
|
||||
**Schritte:**
|
||||
1. Qdrant truncate
|
||||
2. Import (Create-Fall)
|
||||
3. Import (Idempotenz-Test)
|
||||
4. Hash-Reporter
|
||||
5. Export
|
||||
6. Vergleich Vault vs. Export
|
||||
7. Sync-Deletes Test
|
||||
|
||||
---
|
||||
|
||||
## 4. Test-Szenarien
|
||||
|
||||
### 4.1 Chunking-Tests
|
||||
|
||||
**Was wird getestet:**
|
||||
- Sliding Window Strategie
|
||||
- Heading-basierte Strategie
|
||||
- Edge-Vererbung
|
||||
- Chunk-Größen-Limits
|
||||
|
||||
**Tests:**
|
||||
- `tests/test_smart_chunking_integration.py`
|
||||
- `scripts/preview_chunks.py` (Manuelle Inspektion)
|
||||
|
||||
### 4.2 Retrieval-Tests
|
||||
|
||||
**Was wird getestet:**
|
||||
- Semantic Search
|
||||
- Hybrid Search (Semantik + Graph)
|
||||
- Scoring-Formel
|
||||
- Explanation Layer
|
||||
|
||||
**Tests:**
|
||||
- `tests/test_retriever_basic.py`
|
||||
- `tests/test_retriever_edges.py`
|
||||
- `tests/test_retriever_weight.py`
|
||||
- `tests/test_explanation_smoke.py`
|
||||
|
||||
### 4.3 Edge-Tests
|
||||
|
||||
**Was wird getestet:**
|
||||
- Edge-Erstellung (explicit, smart, rule)
|
||||
- Edge-Validierung (Registry)
|
||||
- Edge-Vererbung
|
||||
- Unresolved References
|
||||
|
||||
**Tests:**
|
||||
- `tests/test_edges_all.py`
|
||||
- `tests/test_edges_smoke.py`
|
||||
- `tests/test_edges_defaults_smoke.py`
|
||||
- `scripts/edges_full_check.py`
|
||||
|
||||
### 4.4 Chat & Intent-Tests (WP-25)
|
||||
|
||||
**Was wird getestet:**
|
||||
- Intent-Erkennung (FACT_WHAT, FACT_WHEN, DECISION, EMPATHY, CODING, INTERVIEW)
|
||||
- Hybrid Router (Keyword Fast-Path + LLM Slow-Path)
|
||||
- Decision Engine (Multi-Stream Orchestration)
|
||||
- Parallele Stream-Abfragen (Values, Facts, Biography, Risk, Tech)
|
||||
- Stream-Tracing (`stream_origin` Markierung)
|
||||
- Wissens-Synthese (Template-basierte Zusammenführung)
|
||||
- Interview-Modus
|
||||
- Feedback-Loop
|
||||
|
||||
**Tests:**
|
||||
- `tests/test_wp06_decision.py` - Decision Engine (Legacy)
|
||||
- `tests/test_interview_intent.py` - Interview-Modus
|
||||
- `tests/test_chat_wp05.py` - Chat-Backend (Legacy)
|
||||
- `tests/test_feedback_smoke.py` - Feedback-Loop
|
||||
|
||||
**WP-25 Spezifische Tests (geplant):**
|
||||
- Multi-Stream Retrieval (parallele Abfragen)
|
||||
- Stream-Tracing (stream_origin Zuordnung)
|
||||
- Template-Robustheit (Pre-Initialization)
|
||||
- Intent-Kollision (Keyword-Fast-Path Präzision)
|
||||
|
||||
### 4.5 Ingestion-Tests
|
||||
|
||||
**Was wird getestet:**
|
||||
- Two-Pass Workflow (Pre-Scan, Semantic Processing)
|
||||
- Phase 3 Agentic Edge Validation (WP-24c v4.5.8)
|
||||
- Change Detection (Hash-basiert)
|
||||
- Background Tasks
|
||||
- Smart Edge Allocation
|
||||
- Automatische Spiegelkanten (Invers-Logik)
|
||||
|
||||
**Tests:**
|
||||
- `tests/test_dialog_full_flow.py`
|
||||
- `tests/test_WP22_intelligence.py`
|
||||
- `scripts/import_markdown.py` (mit `--dry-run`)
|
||||
|
||||
**WP-24c Spezifische Tests (geplant):**
|
||||
- candidate: Präfix-Setzung (Links in `### Unzugeordnete Kanten`)
|
||||
- Phase 3 Validierung (VERIFIED/REJECTED)
|
||||
- Kontext-Optimierung (Note-Scope nutzt Note-Summary, Chunk-Scope nutzt Chunk-Text)
|
||||
- Automatische Spiegelkanten (Invers-Logik)
|
||||
- Fehlertoleranz (transient vs. permanent)
|
||||
- Rejected Edges Tracking (Kanten werden nicht persistiert)
|
||||
|
||||
---
|
||||
|
||||
## 5. Continuous Integration
|
||||
|
||||
### 5.1 Pre-Commit Checks
|
||||
|
||||
**Empfohlene Checks:**
|
||||
```bash
|
||||
# Linting
|
||||
flake8 app/ scripts/
|
||||
|
||||
# Type Checking (optional)
|
||||
mypy app/
|
||||
|
||||
# Unit Tests
|
||||
pytest tests/ -k "test_" --ignore=tests/test_*_smoke.py
|
||||
```
|
||||
|
||||
### 5.2 CI/CD Pipeline
|
||||
|
||||
**Gitea Actions:** `.gitea/workflows/`
|
||||
|
||||
**Typische Pipeline:**
|
||||
1. Checkout
|
||||
2. Python Setup
|
||||
3. Dependencies installieren
|
||||
4. Unit Tests
|
||||
5. Integration Tests (optional)
|
||||
6. Deployment (bei main branch)
|
||||
|
||||
---
|
||||
|
||||
## 6. Test-Best Practices
|
||||
|
||||
### 6.1 Isolation
|
||||
|
||||
- **Unit Tests:** Keine externen Abhängigkeiten (Mock alles)
|
||||
- **Integration Tests:** Echte Qdrant, aber isolierte Collections
|
||||
- **E2E Tests:** Separate Test-Umgebung (Port 8002)
|
||||
|
||||
### 6.2 Test-Daten
|
||||
|
||||
- Nutze `make_test_vault.py` für konsistente Test-Daten
|
||||
- Separate Collection-Prefixes für Tests
|
||||
- Cleanup nach Tests (oder isolierte Collections)
|
||||
|
||||
### 6.3 Performance
|
||||
|
||||
- Unit Tests sollten < 1 Sekunde dauern
|
||||
- Integration Tests können länger dauern (10-30 Sekunden)
|
||||
- E2E Tests sind langsam (1-5 Minuten)
|
||||
|
||||
### 6.4 Wartbarkeit
|
||||
|
||||
- Klare Test-Namen (`test_<functionality>_<scenario>`)
|
||||
- Dokumentation in Test-Docstrings
|
||||
- Keine Hardcoded-Pfade (nutze `os.path`)
|
||||
|
||||
---
|
||||
|
||||
## 7. Debugging & Diagnose
|
||||
|
||||
### 7.1 Test-Debugging
|
||||
|
||||
**Pytest Debug-Modus:**
|
||||
```bash
|
||||
pytest tests/test_retriever_basic.py -v --pdb
|
||||
```
|
||||
|
||||
**Einzelnen Test inspizieren:**
|
||||
```bash
|
||||
python3 -m scripts.payload_dryrun --vault ./test_vault --note-id "test-note"
|
||||
```
|
||||
|
||||
### 7.2 Qdrant-State prüfen
|
||||
|
||||
```bash
|
||||
# Collection-Status
|
||||
python3 -m scripts.debug_qdrant_state --prefix mindnet_test
|
||||
|
||||
# Payload-Indexe prüfen
|
||||
python3 -m scripts.diag_payload_indexes --prefix mindnet_test
|
||||
```
|
||||
|
||||
### 7.3 Chunk-Inspektion
|
||||
|
||||
```bash
|
||||
# Chunks für eine Note anzeigen
|
||||
python3 -m scripts.dump_note_chunks --note-id "test-note" --prefix mindnet_test
|
||||
|
||||
# Chunk-Text-Verifikation
|
||||
python3 -m scripts.verify_chunk_texts --prefix mindnet_test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Test-Checkliste für Pull Requests
|
||||
|
||||
Vor jedem PR sollten folgende Tests durchlaufen:
|
||||
|
||||
- [ ] Unit Tests: `pytest tests/ -k "test_" --ignore=tests/test_*_smoke.py`
|
||||
- [ ] Payload-Validierung: `python3 -m scripts.payload_dryrun --vault ./test_vault`
|
||||
- [ ] Graph-Integrität: `python3 -m scripts.edges_full_check`
|
||||
- [ ] Smoke Test (wenn API läuft): `python tests/test_wp06_decision.py -p 8002`
|
||||
|
||||
---
|
||||
|
||||
## 9. Weitere Informationen
|
||||
|
||||
- **Developer Guide:** Siehe [Developer Guide](05_developer_guide.md)
|
||||
- **Scripts-Übersicht:** Siehe [Developer Guide - Scripts](05_developer_guide.md#44-scripts--tooling-die-admin-toolbox)
|
||||
- **Troubleshooting:** Siehe [Admin Operations - Troubleshooting](../04_Operations/04_admin_operations.md#33-troubleshooting-guide)
|
||||
|
||||
---
|
||||
|
||||
**Letzte Aktualisierung:** 2025-01-XX
|
||||
**Version:** 2.9.1
|
||||
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
<!-- DOCUMENT 1: pflichtenheft_obsidian_plugin_mindnet_assistant.md -->
|
||||
---
|
||||
id: pflichtenheft_obsidian_plugin_mindnet_assistant
|
||||
title: Pflichtenheft – Obsidian Plugin „Mindnet Causal Assistant“
|
||||
type: specification
|
||||
status: draft
|
||||
created: 2026-01-13
|
||||
lang: de
|
||||
---
|
||||
|
||||
# Pflichtenheft – Obsidian Plugin „Mindnet Causal Assistant“
|
||||
|
||||
## 1. Zielsetzung
|
||||
Das Plugin unterstützt den Nutzer beim Erstellen und Pflegen eines narrativ-kausalen Wissensgraphen in Obsidian (Mindnet).
|
||||
|
||||
Es bietet:
|
||||
- **Aktive Authoring-Unterstützung** (Guided Note Creation / Interview Flow)
|
||||
- **Kausalketten-Prüfung** (Chain Explorer, Vorwärts/Rückwärts)
|
||||
- **Linting + Auto-Fixes** (Graph-Hygiene, konsistente Kanten, Namensnormalisierung)
|
||||
- **Export/Integration** der strukturierten Graph-Daten für ein Retrieval-System (Qdrant + Graph-Index)
|
||||
|
||||
## 2. Kontext & Randbedingungen
|
||||
- Obsidian Vault enthält Notes als Markdown, jede Entität eine Datei.
|
||||
- Notes enthalten Frontmatter (`id,title,type,status,...`) und Edges in Callout-Blöcken.
|
||||
- Edge-Vokabular inklusive Aliasse & Inversen ist verfügbar (z.B. `edge_vocabulary.md`).
|
||||
- Der Graph soll später von Mindnet traversierbar sein (Vorwärts/Rückwärts über inverse Relationen).
|
||||
- Plugin muss offline nutzbar sein; optionale Backend/LLM-Integration ist konfigurierbar.
|
||||
|
||||
## 3. Begriffsdefinitionen
|
||||
- **Node**: eine Obsidian Markdown-Datei (Entität).
|
||||
- **Edge**: gerichtete Beziehung zwischen zwei Nodes (canonical edge type).
|
||||
- **Alias**: alternative Edge-Bezeichnung in Notes, die auf canonical mapped wird.
|
||||
- **Inverse**: Gegenkante zu einer Edge, laut Vokabular (z.B. `resulted_in` ⇄ `caused_by`).
|
||||
- **Hub/Index**: Note, die primär navigiert (typisch `type: insight`), keine Kausalursache.
|
||||
|
||||
## 4. Scope
|
||||
### In Scope (MVP → V2)
|
||||
**MVP**
|
||||
- Parser für Frontmatter + Edge-Callouts
|
||||
- Normalizer: Alias → Canonical; optional Inversen-Erkennung
|
||||
- Linter: Regelset (Error/Warn/Info) + Report
|
||||
- Chain Explorer: forward/backward traversal (1–4 hops) ab aktueller Note
|
||||
- Quickfixes: Edge-Typ ersetzen, Links normalisieren, Missing Note Stub erzeugen
|
||||
|
||||
**V2**
|
||||
- Guided Authoring (Interview Flow) für `experience/decision/principle/state/strategy`
|
||||
- Refactor Mode: Text → Node/Edge-Kandidaten + Review UI
|
||||
- Export/Sync: JSON Graph Export + optional Qdrant Sync (separater Service)
|
||||
|
||||
### Out of Scope (initial)
|
||||
- Vollautomatische Kausalitäts-Interpretation ohne User-Bestätigung
|
||||
- Vollständige UI-Graph-Visualisierung (Obsidian Graph View bleibt nutzbar)
|
||||
- Direkte medizinische/psychologische Beratung
|
||||
|
||||
## 5. Nutzerrollen & Use Cases
|
||||
### Rolle: Nutzer (Author)
|
||||
- UC1: „Ich schreibe eine Note und will Kanten prüfen“
|
||||
- UC2: „Ich will von einem Ereignis aus die Kausalkette sehen“
|
||||
- UC3: „Ich will eine neue Experience/Decision Note sauber anlegen“
|
||||
- UC4: „Ich habe Text und will daraus Kandidaten extrahieren“
|
||||
- UC5: „Ich will leere Links als open_question sauber erzeugen“
|
||||
|
||||
### Rolle: System/Indexer (Mindnet)
|
||||
- UC6: „Ich brauche exportierbare adjacency lists + canonical edges“
|
||||
|
||||
## 6. Funktionale Anforderungen (FR)
|
||||
|
||||
### FR1: Vault Parsing
|
||||
- FR1.1 Parse Frontmatter (YAML): `id,title,type,status,date,tags,...`
|
||||
- FR1.2 Parse Edge-Callouts im Format:
|
||||
- `> [!abstract]- 🕸️ Semantic Mapping`
|
||||
- `>> [!edge] <relation>`
|
||||
- `>> [[target]]`
|
||||
- FR1.3 Extrahiere alle WikiLinks `[[...]]` aus Edge-Blocks
|
||||
- FR1.4 Erkenne Datei-Pfade, Dateinamen und canonical node identifiers (Dateiname ohne `.md`)
|
||||
|
||||
**Output (intern)**
|
||||
```ts
|
||||
type Node = { id?: string; title?: string; type?: string; status?: string; path: string; slug: string };
|
||||
type Edge = { srcSlug: string; dstSlug: string; rawType: string; canonicalType?: string; line?: number; blockId?: string };
|
||||
```
|
||||
|
||||
### FR2: Edge Normalization
|
||||
- FR2.1 Map rawType/Alias auf canonical edge type via Edge Vocabulary
|
||||
- FR2.2 Speichere Mapping-Entscheidungen (raw → canonical) pro Edge
|
||||
- FR2.3 Liefere inverse edge type (`inverseType`) pro canonical edge type (sofern definiert)
|
||||
|
||||
### FR3: Linting
|
||||
- FR3.1 Führe Checkliste von Regeln aus (siehe separates Dokument)
|
||||
- FR3.2 Liefere LintReport mit Severity (ERROR/WARN/INFO), Location (file, line), Fix-Vorschlägen
|
||||
- FR3.3 Quickfix: wende Fix auf Note an (Text edit), mit Preview/Diff
|
||||
|
||||
### FR4: Chain Explorer (Traversal)
|
||||
- FR4.1 Startpunkt: aktuelle Note im Editor
|
||||
- FR4.2 Forward traversal: `resulted_in`, `followed_by`, `impacts`, `source_of`, optional `related_to`
|
||||
- FR4.3 Backward traversal: `caused_by`, `preceeded_by`, `derived_from`, `impacted_by`
|
||||
- FR4.4 Konfigurierbare maxHops (Default 3)
|
||||
- FR4.5 Ergebnis als Liste von Pfaden + kompaktes Subgraph-Summary (Nodes/Edges)
|
||||
|
||||
### FR5: Missing Notes / Stubs
|
||||
- FR5.1 Erkenne, wenn Edge-Target nicht existiert
|
||||
- FR5.2 Biete „Create Stub Note“ an:
|
||||
- Template basierend auf Typ (default `open_question` wenn unbekannt)
|
||||
- Einhaltung Naming-Rules: `a-z0-9_`
|
||||
- FR5.3 Optional: convert TODO-Link → open_question note
|
||||
|
||||
### FR6: Guided Authoring (V2)
|
||||
- FR6.1 Wizard für neue Notes: Auswahl Typ → Fragen (eine nach der anderen)
|
||||
- FR6.2 Wizard erstellt Datei mit Template + initialen Edge-Blocks
|
||||
- FR6.3 Jede automatische Edge-Vermutung ist „review-required“
|
||||
|
||||
### FR7: Refactor Mode (V2)
|
||||
- FR7.1 Extrahiere aus aktuellem Note-Text Kandidaten:
|
||||
- Event-Kandidaten (Datum/Ort/Verben)
|
||||
- Decision-Kandidaten („entschied“, „nahm an“, „wechselte“)
|
||||
- Principle-Kandidaten („ich glaube“, „ich habe gelernt“)
|
||||
- Relation-Kandidaten („dadurch“, „führte zu“, „weil“)
|
||||
- FR7.2 UI-Review: Checkbox-Liste zum Erstellen/Verwerfen
|
||||
- FR7.3 Generiere Notes + Edges nur nach Bestätigung
|
||||
|
||||
### FR8: Export (MVP optional / V2 empfohlen)
|
||||
- FR8.1 Export JSON: Nodes + canonical edges + inverses
|
||||
- FR8.2 Export adjacency list pro node slug
|
||||
- FR8.3 Optional: webhook/CLI hook für Indexer (Qdrant)
|
||||
|
||||
## 7. Nicht-funktionale Anforderungen (NFR)
|
||||
- NFR1: Performant bei 10k Notes (incremental parse, caching)
|
||||
- NFR2: Offline-first; LLM/Backend optional
|
||||
- NFR3: Deterministische Normalisierung (gleiches Input → gleiches Output)
|
||||
- NFR4: Kein Erfinden von Fakten: Auto-Edge nur als Vorschlag
|
||||
- NFR5: Sicheres Editieren (Diff/Undo via Obsidian APIs)
|
||||
- NFR6: Konfigurierbarkeit (YAML/Settings Tab): maxHops, allowed edges, strict mode
|
||||
|
||||
## 8. Technisches Lösungsdesign
|
||||
|
||||
### 8.1 Obsidian Plugin Struktur (TypeScript)
|
||||
- `main.ts` – Plugin lifecycle, commands, views
|
||||
- `settings.ts` – Einstellungen
|
||||
- `parser/` – Markdown + callout parser
|
||||
- `graph/` – Node/Edge model, normalization, traversal
|
||||
- `lint/` – Rules engine, reports, quickfixes
|
||||
- `ui/` – Sidebar view, modals, diff preview
|
||||
|
||||
### 8.2 Speicherung / Cache
|
||||
- In-memory cache: map `path → parsed Node + edges + hash`
|
||||
- Incremental update: on file change events re-parse only changed file
|
||||
- Optional persisted cache in `.obsidian/plugins/.../cache.json`
|
||||
|
||||
### 8.3 Edge Vocabulary Integration
|
||||
- Input: `edge_vocabulary.md` im Vault ODER eingebettete JSON Ressource
|
||||
- Parsing:
|
||||
- canonical edge types
|
||||
- alias list
|
||||
- inverse mapping
|
||||
- Fallback: minimal builtin vocabulary, wenn Datei fehlt
|
||||
|
||||
### 8.4 Traversal Engine
|
||||
- Graph Index: adjacency lists aus canonical edges
|
||||
- Traversal:
|
||||
- BFS mit hop limit
|
||||
- optional weighted expansion (für Chain Explorer „relevant paths first“)
|
||||
|
||||
### 8.5 Quickfix Engine
|
||||
- Applies patches auf Markdown:
|
||||
- Replace edge type token im Callout (`>> [!edge] ...`)
|
||||
- Rename link targets (replace `[[old]]` → `[[new]]`)
|
||||
- Insert stub note file from template
|
||||
- Safety:
|
||||
- Show diff modal
|
||||
- Use Obsidian editor transactions / file API
|
||||
|
||||
### 8.6 Optional Backend / LLM (V2)
|
||||
- Backend (local node service) für:
|
||||
- text extraction (Refactor Mode)
|
||||
- suggestion generation
|
||||
- Communication:
|
||||
- HTTP local (`127.0.0.1`) oder WebSocket
|
||||
- API key storage via Obsidian settings (encrypted if possible)
|
||||
- Claude-code/Cursor: nutzt Code-Agent für Implementierung, nicht zur Runtime.
|
||||
|
||||
## 9. UI/UX Anforderungen
|
||||
|
||||
### 9.1 Sidebar View „Mindnet Assistant“
|
||||
Tabs:
|
||||
- **Validate**: Lint Report + Fix Buttons
|
||||
- **Chains**: Forward/Backward Chain Explorer + Copy as text
|
||||
- **Create** (V2): Wizard new note
|
||||
- **Refactor** (V2): Extract candidates
|
||||
|
||||
### 9.2 Commands (Command Palette)
|
||||
- `Mindnet: Validate current note`
|
||||
- `Mindnet: Validate vault (selected folders)`
|
||||
- `Mindnet: Show chains from current note`
|
||||
- `Mindnet: Normalize edges in current note`
|
||||
- `Mindnet: Create stub for missing links`
|
||||
- (V2) `Mindnet: Start guided authoring`
|
||||
- (V2) `Mindnet: Refactor current note to graph`
|
||||
|
||||
## 10. Akzeptanzkriterien
|
||||
- AK1: Plugin erkennt Edge-Callouts und normalisiert Aliasse deterministisch
|
||||
- AK2: Linter findet Node-Splitting (mindestens Levenshtein/ähnliche Slugs) und Missing Notes
|
||||
- AK3: Chain Explorer liefert identische Pfade vorwärts/rückwärts bei inversen Edge-Paaren
|
||||
- AK4: Quickfix ersetzt Edge-Typen ohne Markdown zu zerstören; Undo funktioniert
|
||||
- AK5: Export JSON enthält canonical edges + inverse types
|
||||
|
||||
## 11. Deliverables
|
||||
- Obsidian Plugin (TS) mit MVP Features
|
||||
- Dokumentation:
|
||||
- Install/Build
|
||||
- Settings
|
||||
- Rule reference
|
||||
- Example vault sample
|
||||
- Optional: JSON Export Format Spec (nodes/edges)
|
||||
|
||||
---
|
||||
|
||||
## 12. Prompts für Code-Agenten (Cursor / Claude-code)
|
||||
|
||||
### Prompt A (Repo Scaffold + MVP)
|
||||
> Du bist ein Senior TypeScript Engineer. Implementiere ein Obsidian Plugin „Mindnet Causal Assistant“.
|
||||
> Ziele MVP:
|
||||
> 1) Parse Frontmatter (YAML) und Edge-Callouts im Format:
|
||||
> `> [!abstract]- 🕸️ Semantic Mapping` → `>> [!edge] <relation>` → `>> [[target]]`.
|
||||
> 2) Normalisiere Edge Aliasse auf canonical edge types (Vokabular als JSON im Code; später ersetzbar).
|
||||
> 3) Baue eine Sidebar View mit Tabs „Validate“ und „Chains“.
|
||||
> 4) Implementiere Lint Regeln: missing target note, alias-not-normalized, hub-has-causal-edge, chronology-vs-causality warning.
|
||||
> 5) Implementiere Chain Explorer (forward/backward, maxHops=3).
|
||||
> 6) Implementiere Quickfix: replace edge type token, create stub note.
|
||||
> Nutze Obsidian APIs, schreibe sauberen TS Code, mit Tests für Parser/Normalizer.
|
||||
|
||||
### Prompt B (Parser Unit Tests)
|
||||
> Schreibe Unit Tests für den Markdown Parser:
|
||||
> - erkennt mehrere Edge-Blocks pro Datei
|
||||
> - erkennt mehrere Targets pro Edge
|
||||
> - liefert line numbers
|
||||
> - ignoriert WikiLinks außerhalb der Semantic Mapping Callouts
|
||||
> Nutze vitest/jest. Erzeuge fixtures.
|
||||
|
||||
### Prompt C (Lint Engine + Quickfix)
|
||||
> Implementiere eine Lint Engine als Rule-Pipeline.
|
||||
> Jede Regel: id, severity, detect(node, graph) -> findings, fix(finding)->patch.
|
||||
> Baue eine Diff Preview Modal und applyPatch über Obsidian file API.
|
||||
> Implementiere zunächst 6 Regeln aus der Checkliste.
|
||||
|
||||
### Prompt D (Vocabulary Loader)
|
||||
> Implementiere einen VocabularyLoader:
|
||||
> - lädt entweder eingebettetes JSON oder eine Vault-Datei `edge_vocabulary.md`
|
||||
> - parst canonical types, aliases, inverse
|
||||
> - bietet getCanonical(raw) und getInverse(canonical)
|
||||
> Fallback auf builtin vocabulary wenn parsing fehlschlägt.
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
<!-- DOCUMENT 2: checklist_lint_regeln_mindnet_assistant.md -->
|
||||
---
|
||||
id: checklist_lint_regeln_mindnet_assistant
|
||||
title: Checkliste – Lint-Regeln für Mindnet Causal Assistant
|
||||
type: specification
|
||||
status: draft
|
||||
created: 2026-01-13
|
||||
lang: de
|
||||
---
|
||||
|
||||
# Checkliste – Lint-Regeln für Mindnet Causal Assistant
|
||||
|
||||
## Severity Levels
|
||||
- **ERROR**: bricht Traversal/Indexer oder erzeugt falsche Nodes
|
||||
- **WARN**: wahrscheinlich falsche Semantik / schlechter Retrieval-Impact
|
||||
- **INFO**: Optimierung / Empfehlung
|
||||
|
||||
---
|
||||
|
||||
## A. Graph-Integrität & Naming
|
||||
|
||||
### L1 (ERROR) Missing Target Note
|
||||
**Wenn:** Edge target `[[X]]` existiert nicht als Datei im Vault
|
||||
**Dann:** Finding `missing_target`
|
||||
**Fix:** „Create Stub Note“ (default `type: open_question`) oder remove edge
|
||||
|
||||
### L2 (ERROR) Node Splitting durch Schreibvarianten
|
||||
**Wenn:** mehrere Targets im Vault sind ähnlich (slug distance), oder in Edges mehrere Varianten vorkommen
|
||||
**Dann:** Finding `node_split_candidate`
|
||||
**Fix:** Vorschlag canonical slug + bulk replace links
|
||||
|
||||
### L3 (ERROR) Invalid Filename Policy
|
||||
**Wenn:** Dateiname enthält Zeichen außerhalb `[a-z0-9_]`
|
||||
**Dann:** Finding `invalid_filename`
|
||||
**Fix:** Rename file + update all backlinks
|
||||
|
||||
---
|
||||
|
||||
## B. Edge-Formale Regeln
|
||||
|
||||
### L4 (ERROR) Unknown Edge Type / Unmapped Alias
|
||||
**Wenn:** raw edge type nicht im Vokabular (alias→canonical)
|
||||
**Dann:** Finding `unknown_edge_type`
|
||||
**Fix:** Edge type ersetzen durch best guess oder user selection
|
||||
|
||||
### L5 (WARN) Alias not normalized
|
||||
**Wenn:** raw edge type ist Alias, canonical bekannt, aber Note enthält Alias
|
||||
**Dann:** Finding `alias_not_normalized`
|
||||
**Fix:** Replace raw with canonical (optional config)
|
||||
|
||||
### L6 (WARN) Missing Inverse Edge (optional strict mode)
|
||||
**Wenn:** Edge A->B existiert, inverse nach Vokabular fehlt in B
|
||||
**Dann:** Finding `missing_inverse`
|
||||
**Fix:** Add inverse edge to target note (review + diff)
|
||||
|
||||
---
|
||||
|
||||
## C. Semantik: Kausalität vs Chronologie
|
||||
|
||||
### L7 (WARN) Chronology used as Causality
|
||||
**Wenn:** Knoten/Target wirkt wie Prozessschritt („warten“, „gehen“, „ankommen“) und Edge type ist `resulted_in`
|
||||
**Dann:** Finding `chronology_as_causality`
|
||||
**Fix:** Vorschlag `followed_by` (inverse `preceeded_by`)
|
||||
|
||||
### L8 (INFO) Kausalität ohne Brücken (Gap)
|
||||
**Wenn:** Pfad springt von Ereignis direkt zu Entscheidung, aber intermediäre Knoten fehlen (heuristisch)
|
||||
**Dann:** Finding `missing_bridge_node`
|
||||
**Fix:** Vorschlag „Create open_question bridge“ (z.B. „Was war der konkrete Auslöser…?“)
|
||||
|
||||
---
|
||||
|
||||
## D. Node-Type Regeln
|
||||
|
||||
### L9 (ERROR) Causal edges from open_question/hypothesis/white_spot
|
||||
**Wenn:** node.type in `{open_question, hypothesis, white_spot}` und Edge type in `{caused_by, resulted_in, impacts, derived_from}`
|
||||
**Dann:** Finding `invalid_causal_edge_from_uncertain`
|
||||
**Fix:** Replace with `related_to` oder remove edge
|
||||
|
||||
### L10 (WARN) Hub/Insight Note trägt Kausalität
|
||||
**Wenn:** node.type == `insight` (oder Hub-Pattern) und hat `caused_by/resulted_in`
|
||||
**Dann:** Finding `hub_has_causality`
|
||||
**Fix:** Replace edges with `related_to` und verschiebe Kausalität in atomare Notes
|
||||
|
||||
### L11 (WARN) Principle uses caused_by for origin
|
||||
**Wenn:** node.type == `principle` und enthält `caused_by` zu Erlebnissen
|
||||
**Dann:** Finding `principle_origin_edge`
|
||||
**Fix:** Vorschlag `derived_from` oder `based_on`
|
||||
|
||||
### L12 (INFO) Decision without caused_by
|
||||
**Wenn:** node.type == `decision` und hat keine `caused_by`-Kanten
|
||||
**Dann:** Finding `decision_without_causes`
|
||||
**Fix:** Wizard: „Was war der Auslöser?“ → create open_question or add edges
|
||||
|
||||
---
|
||||
|
||||
## E. Redundanz & Zyklen
|
||||
|
||||
### L13 (WARN) Duplicate Edges
|
||||
**Wenn:** identische canonical edge mehrfach gesetzt (src,type,dst)
|
||||
**Dann:** Finding `duplicate_edge`
|
||||
**Fix:** remove duplicates
|
||||
|
||||
### L14 (WARN) Cycles in pure causality subgraph
|
||||
**Wenn:** Zyklus ausschließlich über `{caused_by,resulted_in,derived_from,source_of}`
|
||||
**Dann:** Finding `causal_cycle`
|
||||
**Fix:** Markiere zur Review; oft ist eine Kante falsch gerichtet
|
||||
|
||||
---
|
||||
|
||||
## F. Traversal-Optimierung (Retrieval-Wirkung)
|
||||
|
||||
### L15 (INFO) Overuse of related_to
|
||||
**Wenn:** Note enthält nur `related_to` und keine spezifischeren Kanten
|
||||
**Dann:** Finding `weak_semantics`
|
||||
**Fix:** Vorschlag: präzisere Beziehungstypen setzen
|
||||
|
||||
### L16 (INFO) Missing type metadata
|
||||
**Wenn:** Frontmatter `type` fehlt
|
||||
**Dann:** Finding `missing_type`
|
||||
**Fix:** Prompt user to choose type; set via template
|
||||
|
||||
### L17 (INFO) Missing date on experience
|
||||
**Wenn:** node.type == `experience` und kein Datum/Zeitraum vorhanden
|
||||
**Dann:** Finding `missing_date_experience`
|
||||
**Fix:** Prompt: „Wann ungefähr?“ → set `date` oder `time_range`
|
||||
|
||||
---
|
||||
|
||||
## Suggested Implementation Notes
|
||||
- Jede Regel liefert:
|
||||
- `ruleId`, `severity`, `message`, `location`, `evidence`, `quickFixes[]`
|
||||
- QuickFixes sind Patch-Operationen:
|
||||
- replaceText(range, text)
|
||||
- insertBlock(atLine, block)
|
||||
- createFile(path, content)
|
||||
- renameFile(old, new) + updateLinks(glob)
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# 📋 Mindnet Feature-Backlog (V3.1 - V4.0)
|
||||
|
||||
**Projekt:** Mindnet – Der Digitale Zwilling
|
||||
**Status:** Aktiv nach Meilenstein WP-25b
|
||||
**Architektur:** MoE, Multi-Stream RAG, Lazy-Prompt-Orchestration
|
||||
|
||||
---
|
||||
|
||||
## 🟢 Priorisierte Features (V3.1 - V3.5)
|
||||
|
||||
### WP-26: Conversational Soul (Memory & Context)
|
||||
* **Nutzerwert:** Ermöglicht echte Dialoge ("Erkläre mir den Punkt von eben genauer").
|
||||
* **Lösungsskizze:**
|
||||
* Implementierung eines `SessionStore` (SQLite) für Chat-Verläufe.
|
||||
* Erweiterung der `DecisionEngine` um einen `ContextReducer`, der die Historie asynchron zusammenfasst (via Profil `compression_fast`).
|
||||
* Einspeisung der Historie in die Synthese-Prompts via `{history}` Platzhalter.
|
||||
* **Abhängigkeiten:** `DecisionEngine` v1.3.2, `LLMService` v3.5.6.
|
||||
|
||||
### WP-27: Autonomous Tuning (Self-Calibration)
|
||||
* **Nutzerwert:** Das System lernt aus Fehlern und optimiert das Retrieval selbstständig.
|
||||
* **Lösungsskizze:**
|
||||
* Auswertung der JSONL-Feedback-Logs (Daumen hoch/runter).
|
||||
* Automatisierte Anpassung von `edge_boosts` und `top_k` in einer neuen `tuning_registry.json`, die Werte in der `decision_engine.yaml` überschreibt.
|
||||
* **Besonderheit:** Nutzt den `[PROMPT-TRACE]` zur Korrelation von Erfolg und Instruktions-Set.
|
||||
|
||||
### WP-28: Global Discovery Screen (UI)
|
||||
* **Nutzerwert:** Eine zentrale Anlaufstelle für Suche und Wissens-Exploration.
|
||||
* **Lösungsskizze:**
|
||||
* Hybride Suche: Kombination aus Keyword (BM25), Vektor (Semantic) und Graph-Traversierung.
|
||||
* "Reasoning-Trace" Visualisierung: Grafische Darstellung, warum ein Chunk gefunden wurde (Tracing über `stream_origin`).
|
||||
|
||||
---
|
||||
|
||||
## 🟡 Zukünftige Features (Ideenspeicher)
|
||||
|
||||
* **WP-13 (Agentic Layer):** MCP-Server Integration für externe Agenten (Claude/OpenAI).
|
||||
* **WP-21 (Semantic Routing v2):** Dynamisches Intent-Boosting (Bessere Gewichte basierend auf der Frage-Art).
|
||||
* **WP-18 (Graph Health):** Automatisierte Reparatur von "Dangling Edges" und Inconsistencies.
|
||||
* **WP-24 (Knowledge Mining):** Automatisches Erstellen von Notiz-Drafts aus Chat-Erkenntnissen ("Chat-to-Vault").
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# 🏃 Sprint-Planung: Sprint 1 (V3.1)
|
||||
**Fokus:** Intelligenz & Gedächtnis
|
||||
|
||||
## 🎯 Sprint-Ziel
|
||||
Mindnet kann sich an den Kontext des aktuellen Gesprächs erinnern und seine Retrieval-Logik basierend auf historischen Feedback-Daten anpassen.
|
||||
|
||||
## 🛠️ Aufgaben & Lösungsskizzen
|
||||
|
||||
### Aufgabe 1: SQLite Session Management (Backend)
|
||||
* **Datei:** `app/core/retrieval/session_manager.py` (Neu)
|
||||
* **Details:** Erstelle eine DB-Struktur für `sessions` und `messages`.
|
||||
* **Logik:** Jede Anfrage im `chat.py` Endpunkt muss eine `session_id` verarbeiten.
|
||||
|
||||
### Aufgabe 2: Context Injection in DecisionEngine
|
||||
* **Datei:** `app/core/retrieval/decision_engine.py`
|
||||
* **Details:** 1. Abruf der letzten 5 Nachrichten aus dem `SessionManager`.
|
||||
2. Verdichtung der Historie auf max. 500 Token via `llm_service.generate_raw_response(prompt_key="compression_template", ...)`.
|
||||
3. Injection in `_generate_final_answer` als Variable `history`.
|
||||
|
||||
### Aufgabe 3: Das Tuning-Modul (Self-Calibration)
|
||||
* **Datei:** `app/services/tuning_service.py` (Neu)
|
||||
* **Details:** 1. Parser für `logs/feedback.jsonl`.
|
||||
2. Logik: Wenn `negative_feedback` + `intent == 'CODING'`, erhöhe `tech_stream` boost um 0.5.
|
||||
3. Persistenz in `config/tuning_registry.yaml`.
|
||||
|
||||
## 📦 Definition of Done (DoD)
|
||||
- [ ] Test A: Rückfrage "Was meinst du damit?" bezieht sich auf das vorherige Ergebnis.
|
||||
- [ ] Test B: Tuning-Werte werden nach manuellem Feedback in den Logs berücksichtigt.
|
||||
- [ ] Test C: `[PROMPT-TRACE]` zeigt korrekte Level-1/2 Matches für Memory-Prompts.
|
||||
|
|
@ -2,14 +2,14 @@
|
|||
doc_type: roadmap
|
||||
audience: product_owner, developer
|
||||
status: active
|
||||
version: 4.5.8
|
||||
context: "Aktuelle Planung für kommende Features (ab WP16), Release-Strategie und Historie der abgeschlossenen WPs nach WP-14/15b/15c/25/25a/25b/24c."
|
||||
version: 2.9.1
|
||||
context: "Aktuelle Planung für kommende Features (ab WP16), Release-Strategie und Historie der abgeschlossenen WPs nach WP-14/15b."
|
||||
---
|
||||
|
||||
# Mindnet Active Roadmap
|
||||
|
||||
**Aktueller Stand:** v4.5.8 (Post-WP24c: Phase 3 Agentic Edge Validation - Integrity Baseline)
|
||||
**Fokus:** Chunk-Aware Multigraph-System, Agentic Edge Validation, Graph-Qualitätssicherung.
|
||||
**Aktueller Stand:** v2.9.1 (Post-WP14 / WP-15b)
|
||||
**Fokus:** Modularisierung, Two-Pass Ingestion & Graph Intelligence.
|
||||
|
||||
| Phase | Fokus | Status |
|
||||
| :--- | :--- | :--- |
|
||||
|
|
@ -48,11 +48,6 @@ Eine Übersicht der implementierten Features zum schnellen Auffinden von Funktio
|
|||
| **WP-20** | **Cloud Hybrid Mode & Resilienz** | **Ergebnis:** Integration von OpenRouter (Mistral 7B) & Gemini 2.5 Lite. Implementierung von WP-76 (Rate-Limit Wait) & Mistral-safe JSON Parsing. |
|
||||
| **WP-21** | Semantic Graph Routing & Canonical Edges | Transformation des Graphen von statischen Verbindungen zu dynamischen, kontextsensitiven Pfaden. Das System soll verstehen, *welche* Art von Verbindung für die aktuelle Frage relevant ist ("Warum?" vs. "Was kommt danach?"). |
|
||||
| **WP-22** | **Content Lifecycle & Registry** | **Ergebnis:** SSOT via `01_edge_vocabulary.md`, Alias-Mapping, Status-Scoring (`stable`/`draft`) und Modularisierung der Scoring-Engine. |
|
||||
| **WP-15c** | **Multigraph-Support & Diversity Engine** | **Ergebnis:** Section-basierte Links, Note-Level Diversity Pooling, Super-Edge Aggregation, Provenance Firewall. Transformation zu einem hochpräzisen Multigraphen. |
|
||||
| **WP-25** | **Agentic Multi-Stream RAG Orchestration** | **Ergebnis:** Übergang von linearer RAG-Architektur zu paralleler Multi-Stream Engine. Intent-basiertes Routing (Hybrid Fast/Slow-Path), parallele Wissens-Streams (Values, Facts, Biography, Risk, Tech), Stream-Tracing und Template-basierte Wissens-Synthese. |
|
||||
| **WP-25a** | **Mixture of Experts (MoE) & Fallback-Kaskade** | **Ergebnis:** Profilbasierte Experten-Architektur, rekursive Fallback-Kaskade, Pre-Synthesis Kompression, profilgesteuerte Ingestion und Embedding-Konsolidierung. |
|
||||
| **WP-25b** | **Lazy-Prompt-Orchestration & Full Resilience** | **Ergebnis:** Hierarchisches Prompt-Resolution-System (3-stufig), Lazy-Prompt-Loading, ultra-robustes Intent-Parsing, differenzierte Ingestion-Validierung und PROMPT-TRACE Logging. |
|
||||
| **WP-24c** | **Phase 3 Agentic Edge Validation & Graph Integrity** | **Ergebnis:** Finales Validierungs-Gate für `candidate:` Kanten, dynamische Kontext-Optimierung (Note-Scope vs. Chunk-Scope), Verhinderung von "Geister-Verknüpfungen" und Graph-Qualitätssicherung. Transformation zu einem Chunk-Aware Multigraph-System. |
|
||||
|
||||
### 2.1 WP-22 Lessons Learned
|
||||
* **Architektur:** Die Trennung von `retriever.py` und `retriever_scoring.py` war notwendig, um LLM-Context-Limits zu wahren und die Testbarkeit der mathematischen Formeln zu erhöhen.
|
||||
|
|
@ -196,82 +191,53 @@ Der bisherige WP-15 Ansatz litt unter Halluzinationen (erfundene Kantentypen), h
|
|||
2. **Single Source of Truth (SSOT):** Die Registry nutzt `01_edge_vocabulary.md` als führende Konfiguration.
|
||||
3. **Self-Learning Loop:** Protokollierung unbekannter Kanten in `unknown_edges.jsonl`.
|
||||
|
||||
### WP-25: Agentic Multi-Stream RAG Orchestration
|
||||
**Status:** ✅ Fertig (v3.0.0)
|
||||
### WP-23: Agentic Multi-Stream Reasoning (Mindnet 2025)
|
||||
|
||||
### WP-25a: Mixture of Experts (MoE) & Fallback-Kaskade
|
||||
**Status:** ✅ Fertig (v3.1.0)
|
||||
#### 1. Zielsetzung & Problemstellung
|
||||
Das bisherige System basiert auf einem globalen Scoring-Modell, bei dem Notizen unterschiedlicher Typen (z. B. `insight` vs. `belief`) in einem einzigen Retrieval-Topf konkurrieren. Dies führt dazu, dass leiser gewichtete, aber fundamentale Identitätsmerkmale oft durch hochgewichtete aktuelle Erkenntnisse verdrängt werden. Ziel dieses Pakets ist die Einführung einer parallelen **Stream-Architektur**, um die Vielschichtigkeit menschlicher Entscheidungsprozesse (Werte + Erfahrung + Absicht) im LLM-Kontext zu garantieren.
|
||||
|
||||
**Ergebnis:** Transformation von MindNet von einer provider-basierten Steuerung auf eine **profilbasierte Experten-Architektur (Mixture of Experts)**. Jede Systemaufgabe wird einem dedizierten Profil zugewiesen, das Modell, Provider und Parameter unabhängig definiert.
|
||||
#### 2. Funktionsbeschreibung: Die Streams
|
||||
Die Daten aus der `types.yaml` werden in drei logische Verarbeitungseinheiten unterteilt:
|
||||
|
||||
**Kern-Features:**
|
||||
1. **Experten-Steuerung:** Zentrale Profile-Registry (`llm_profiles.yaml`) für alle LLM-Aufgaben
|
||||
2. **Rekursive Fallback-Kaskade:** Automatische Resilienz bei Provider-Fehlern mit Schutz gegen Zirkel-Referenzen
|
||||
3. **Pre-Synthesis Kompression:** Asynchrone Verdichtung überlanger Wissens-Streams vor der Synthese
|
||||
4. **Profilgesteuerte Ingestion:** Deterministische Validierung via `ingest_validator` (Temperature 0.0)
|
||||
5. **Embedding-Konsolidierung:** Zentrale Steuerung des Embedding-Modells über `embedding_expert` Profil
|
||||
6. **Startup-Schutz:** Validierung der YAML-Konfigurationen beim Booten
|
||||
##### A. Identity Stream (Die Wahrheitsebene)
|
||||
* **Inhalt:** `value`, `belief`, `trait`, `principle`, `need`, `boundary`, `bias`.
|
||||
* **Zweck:** Definition des moralischen Kompasses, der psychologischen Grundbedürfnisse und kognitiven Muster.
|
||||
* **Wirkung:** Liefert das "Warum" hinter jeder Handlung.
|
||||
|
||||
**Technische Details:**
|
||||
- LLM Service v3.5.2: Rekursive Fallback-Kaskade
|
||||
- Decision Engine v1.2.1: Profile-Driven Orchestration
|
||||
- Ingestion Processor v2.14.0: Profilgesteuerte Validierung
|
||||
- Embeddings Client v2.6.0: Profil-basierte Modell-Auflösung
|
||||
- llm_profiles.yaml v1.3.0: Zentrale Experten-Registry
|
||||
- decision_engine.yaml v3.2.2: Decoupled MoE Logic
|
||||
##### B. History Stream (Die Evidenzebene)
|
||||
* **Inhalt:** `experience`, `event`, `source`, `journal`, `person`.
|
||||
* **Zweck:** Bereitstellung empirischer Belege aus der Vergangenheit und sozialer Kontexte.
|
||||
* **Wirkung:** Verankert die Antwort in real erlebten Mustern und Fakten.
|
||||
|
||||
### WP-25b: Lazy-Prompt-Orchestration & Full Resilience
|
||||
**Status:** ✅ Fertig (v3.1.1)
|
||||
##### C. Action Stream (Die Dynamikebene)
|
||||
* **Inhalt:** `project`, `decision`, `goal`, `task`, `risk`, `motivation`, `habit`, `state`.
|
||||
* **Zweck:** Analyse der aktuellen Richtung, geplanter Vorhaben und des gegenwärtigen Zustands.
|
||||
* **Wirkung:** Liefert den Kontext für die Umsetzung und zukünftige Ziele.
|
||||
|
||||
**Ergebnis:** Umstellung von statischer Prompt-Formatierung auf eine **hierarchische Lazy-Prompt-Orchestration**. Prompts werden erst im Moment des Modellaustauschs geladen, basierend auf dem exakt aktiven Modell. Dies ermöglicht modell-spezifisches Tuning und maximale Resilienz bei Modell-Fallbacks.
|
||||
|
||||
**Kern-Features:**
|
||||
1. **Hierarchisches Prompt-Resolution-System:** Dreistufige Auflösung (Modell-ID → Provider → Default)
|
||||
2. **Lazy-Loading:** Prompts werden erst zur Laufzeit geladen, wenn das aktive Modell bekannt ist
|
||||
3. **Ultra-robustes Intent-Parsing:** Regex-basierter Parser bereinigt Modell-Artefakte (z.B. `CODING[/S]` → `CODING`)
|
||||
4. **Differenzierte Ingestion-Validierung:** Unterscheidung zwischen transienten (Netzwerk) und permanenten (Config) Fehlern
|
||||
5. **PROMPT-TRACE Logging:** Vollständige Transparenz über genutzte Instruktionen
|
||||
#### 3. Technische Wirkungsweise (Solution Sketch)
|
||||
|
||||
**Technische Details:**
|
||||
- LLM Service v3.5.5: Hierarchische Prompt-Resolution mit Lazy-Loading
|
||||
- Decision Engine v1.3.2: Ultra-robustes Intent-Parsing via Regex
|
||||
- Ingestion Validation v2.14.0: Lazy-Prompt-Integration, differenzierte Fehlerbehandlung
|
||||
- prompts.yaml v3.2.2: Hierarchische Struktur mit Modell-spezifischen Overrides
|
||||
##### Schritt 1: Query-Decomposition
|
||||
Ein initialer Klassifizierungs-Agent analysiert die Nutzeranfrage und bestimmt, welcher Stream primär angesprochen werden muss (z. B. "Wie soll ich mich entscheiden?" boostet den Identity Stream).
|
||||
|
||||
**Ausblick (WP-25c):**
|
||||
- Kontext-Budgeting: Intelligente Token-Verteilung
|
||||
- Stream-specific Provider: Unterschiedliche KI-Modelle pro Wissensbereich
|
||||
- Erweiterte Prompt-Optimierung: Dynamische Anpassung basierend auf Kontext und Historie
|
||||
##### Schritt 2: Parallel Stream Retrieval
|
||||
Anstelle einer Suche werden drei unabhängige Vektor-Suchen mit Typ-Filtern durchgeführt:
|
||||
* **Search_A (Identity):** Top-5 Ergebnisse aus Identitäts-Notizen.
|
||||
* **Search_B (History):** Top-5 Ergebnisse aus biografischen/externen Notizen.
|
||||
* **Search_C (Action):** Top-5 Ergebnisse aus operativen/strategischen Notizen.
|
||||
|
||||
### WP-24c: Phase 3 Agentic Edge Validation & Graph Integrity
|
||||
**Status:** ✅ Fertig (v4.5.8)
|
||||
##### Schritt 3: Agentic Synthesis (The Reasoning)
|
||||
Ein Synthese-Agent (LLM) erhält die aggregierten Ergebnisse in getrennten Sektionen. Die Anweisung lautet:
|
||||
1. **Prüfung:** Steht das aktuelle Vorhaben (Action) im Einklang mit den Werten (Identity)?
|
||||
2. **Abgleich:** Welche vergangenen Erfahrungen (History) stützen oder widersprechen diesem Weg?
|
||||
3. **Korrektur:** Identifiziere mögliche Biases oder Grenzüberschreitungen (Boundary).
|
||||
|
||||
**Ergebnis:** Transformation des Systems von einem dokumentenbasierten RAG zu einem **Chunk-Aware Multigraph-System** mit finalem Validierungs-Gate für alle `candidate:` Kanten. Verhindert "Geister-Verknüpfungen" und sichert die Graph-Qualität durch agentische LLM-Validierung.
|
||||
|
||||
**Kern-Features:**
|
||||
1. **Phase 3 Validierungs-Gate:** Finales Validierungs-Gate für alle Kanten mit `candidate:` Präfix in `rule_id` oder `provenance`
|
||||
2. **Dynamische Kontext-Optimierung:** Intelligente Kontext-Auswahl basierend auf `scope`:
|
||||
- **Note-Scope:** Nutzt `note_summary` (Top 5 Chunks) oder `note_text` (aggregierter Gesamttext)
|
||||
- **Chunk-Scope:** Nutzt spezifischen Chunk-Text, falls verfügbar, sonst Fallback auf Note-Text
|
||||
3. **Agentic Edge Validation:** LLM-basierte semantische Prüfung via `ingest_validator` Profil (Temperature 0.0)
|
||||
4. **Fehlertoleranz:** Differenzierte Behandlung von transienten (Netzwerk) vs. permanenten (Config) Fehlern
|
||||
5. **Graph-Qualitätssicherung:** Rejected Edges werden **nicht** in die Datenbank geschrieben, verhindert persistente "Geister-Verknüpfungen"
|
||||
|
||||
**Technische Details:**
|
||||
- Ingestion Processor v4.5.8: 3-Phasen-Modell (Pre-Scan, Semantic Processing, Phase 3 Validation)
|
||||
- Ingestion Validation v2.14.0: `validate_edge_candidate()` mit MoE-Integration
|
||||
- Kontext-Optimierung: Note-Summary/Text für Note-Scope, Chunk-Text für Chunk-Scope
|
||||
- Logging: `🚀 [PHASE 3]` für Start, `✅ [PHASE 3] VERIFIED` für Erfolg, `🚫 [PHASE 3] REJECTED` für Ablehnung
|
||||
|
||||
**System-Historie (v4.1.0 - v4.5.8):**
|
||||
- v4.1.0 (Gold-Standard): Einführung der Scope-Awareness und Section-Filterung
|
||||
- v4.4.1 (Clean-Context): Entfernung technischer Callouts vor Vektorisierung
|
||||
- v4.5.0 - v4.5.3: Debugging & Härtung (Pydantic EdgeDTO, Retrieval-Tracer)
|
||||
- v4.5.4: Attribut-Synchronisation (QueryHit-Modelle)
|
||||
- v4.5.5: Effizienz-Optimierung (Context-Persistence)
|
||||
- v4.5.7: Stabilitäts-Fix & Zonen-Mapping (UnboundLocalError, Zonen-Inversion)
|
||||
- v4.5.8: Agentic Validation Gate (Phase 3, Kontext-Optimierung, Audit verifiziert)
|
||||
|
||||
#### 4. Erwartete Ergebnisse
|
||||
* **Höhere Resonanz:** Antworten wirken authentischer, da sie explizit auf das Wertesystem des Nutzers Bezug nehmen.
|
||||
* **Widerspruchs-Erkennung:** Das System kann den Nutzer aktiv warnen, wenn ein Projekt gegen seine `principles` oder `needs` verstößt.
|
||||
* **Robustes Retrieval:** Wichtige Identitäts-Informationen gehen nicht mehr im "Rauschen" von hunderten Journal-Einträgen verloren.
|
||||
---
|
||||
|
||||
### WP-24 – Proactive Discovery & Agentic Knowledge Mining
|
||||
|
|
|
|||
|
|
@ -254,9 +254,6 @@ Bitte bestätige die Übernahme, erstelle die `edge_mappings.yaml` Struktur und
|
|||
* Es ist der logisch perfekte Ort, um zu sagen: "Wenn der User im Analyse-Modus ist, sind Fakten-Kanten wichtiger als Gefühls-Kanten."
|
||||
|
||||
Damit hast du das perfekte Paket für den nächsten Entwicklungsschritt geschnürt!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WP-22: Content Lifecycle & Meta-Config
|
||||
|
||||
|
|
@ -274,6 +271,8 @@ Wir haben eine Markdown-Datei (`01_edge_vocabulary.md`), die als Single-Source-o
|
|||
**Dein Auftrag:**
|
||||
Implementiere (A) den Content-Lifecycle, (B) die Edge-Registry und (C) das Semantic Routing.
|
||||
|
||||
---
|
||||
|
||||
### Teil A: Content Lifecycle (Ingestion Logic)
|
||||
Steuerung über Frontmatter `status`:
|
||||
1. **System-Dateien (No-Index):**
|
||||
|
|
@ -282,6 +281,8 @@ Steuerung über Frontmatter `status`:
|
|||
* Wenn `status` in `['draft', 'active', 'stable']`: Status wird im Payload gespeichert.
|
||||
* **ToDo:** Erweitere `scoring.py`, damit `stable` Notizen einen Bonus erhalten (`x 1.2`), `drafts` einen Malus (`x 0.5`).
|
||||
|
||||
---
|
||||
|
||||
### Teil B: Central Edge Registry & Validation
|
||||
1. **Registry Klasse:**
|
||||
* Erstelle `EdgeRegistry` (Singleton).
|
||||
|
|
@ -291,6 +292,8 @@ Steuerung über Frontmatter `status`:
|
|||
* Prüfe beim Import jede Kante gegen die Registry.
|
||||
* Unbekannte Typen werden **nicht** verworfen, sondern in `data/logs/unknown_edges.jsonl` geloggt (für späteres Review).
|
||||
|
||||
---
|
||||
|
||||
### Teil C: Semantic Graph Routing (Dynamic Boosting)
|
||||
**Ziel:** Die Bedeutung einer Kante soll sich je nach Frage-Typ ändern ("Warum" vs. "Wie").
|
||||
|
||||
|
|
@ -305,6 +308,7 @@ Die Gewichtung findet **Pre-Retrieval** (im Scoring-Algorithmus) statt, **nicht*
|
|||
* Der `Retriever` erhält vom Router die `boost_edges` Map.
|
||||
* Berechne Score: `BaseScore * (1 + ConfigWeight + DynamicBoost)`.
|
||||
|
||||
---
|
||||
|
||||
**Deine Aufgaben:**
|
||||
1. Zeige die `EdgeRegistry` Klasse (Parsing Logik).
|
||||
|
|
@ -312,12 +316,10 @@ Die Gewichtung findet **Pre-Retrieval** (im Scoring-Algorithmus) statt, **nicht*
|
|||
3. Zeige die Erweiterung in `scoring.py` (Status-Gewicht & Dynamic Edge Boosting).
|
||||
|
||||
Bitte bestätige die Übernahme dieses Architektur-Pakets.
|
||||
```
|
||||
|
||||
## WP24
|
||||
```text
|
||||
---
|
||||
|
||||
Übergabe Arbeitspaket: WP-24 – Proactive Discovery & Agentic Knowledge Mining
|
||||
# Übergabe Arbeitspaket: WP-24 – Proactive Discovery & Agentic Knowledge Mining
|
||||
|
||||
## 1. Projekt-Kontext
|
||||
Wir arbeiten an **Mindnet**, einem System für einen "digitalen Zwilling". Das System nutzt einen Wissensgraph (Qdrant), asynchrone Ingestion und eine hybride LLM-Infrastruktur (Cloud/Lokal).
|
||||
|
|
@ -355,81 +357,3 @@ Stelle sicher, dass dir folgende Dateien vorliegen, um die Logik zu verstehen un
|
|||
1. Entwurf eines `RecommenderService` für die Vektor-Suche in Qdrant.
|
||||
2. Integration des Services in die `ingestion.py` zur automatischen Befüllung des `candidate_pool`.
|
||||
3. Erweiterung des Chat-Backends um die "Capture-to-Vault" Funktionalität.
|
||||
|
||||
|
||||
```
|
||||
|
||||
## WP-25: Advanced Reasoning Engine (Agentic RAG)
|
||||
|
||||
``` Text
|
||||
**Status:** 🚀 High-Priority Upgrade
|
||||
**Ziel:** Implementierung einer mehrstufigen Entscheidungs-Architektur ("Multi-Hop").
|
||||
|
||||
|
||||
**Das Problem:**
|
||||
Single-Step-Retrieval verwässert Ergebnisse. Eine Suche nach "Projekt-Entscheidung" findet oft operative Details, übersieht aber fundamentale Werte ("Wertekompass"), weil diese semantisch distanziert sind.
|
||||
|
||||
**Die Lösung (Architektur):**
|
||||
Wir ersetzen die flache Suche durch eine **Orchestrator-Logik**, die parallele Streams ausführt.
|
||||
|
||||
---
|
||||
|
||||
### Teil A: Configuration Upgrade (`decision_engine.yaml`)
|
||||
Erweitere die Config-Struktur, um statt einfacher Listen echte **Sub-Queries** zu definieren.
|
||||
|
||||
*Neu (Vorschlag):*
|
||||
````yaml
|
||||
strategies:
|
||||
DECISION:
|
||||
description: "Komplexe Abwägung"
|
||||
# Der Orchestrator führt diese 3 Streams parallel aus:
|
||||
streams:
|
||||
- name: "facts"
|
||||
query_template: "Status und Fakten zu: {query}"
|
||||
filter_types: ["project", "journal", "decision"]
|
||||
edge_boosts: {part_of: 1.5}
|
||||
|
||||
- name: "values"
|
||||
query_template: "Welche Werte und Prinzipien sind relevant für: {query}"
|
||||
filter_types: ["value", "principle", "belief"]
|
||||
edge_boosts: {derived_from: 2.0, enforced_by: 2.0} # Findet den "Wertekompass"!
|
||||
|
||||
- name: "risks"
|
||||
query_template: "Risiken und Gefahren bei: {query}"
|
||||
filter_types: ["risk", "obstacle"]
|
||||
edge_boosts: {blocks: 2.5, impacts: 2.0}
|
||||
````
|
||||
``` Text
|
||||
### Teil B: Implementation (`DecisionEngine.py`)
|
||||
1. **Decomposition:** Wenn eine Strategie `streams` definiert, darf nicht mehr *ein* `retrieve()` Aufruf erfolgen.
|
||||
2. **Parallel Execution:**
|
||||
* Iteriere über alle Streams.
|
||||
* Führe für jeden Stream einen eigenen `retriever.retrieve()` aus – mit dessen spezifischen Filtern, Query und Edge-Boosts.
|
||||
3. **Intermediate Summarization (Optional/Later):**
|
||||
* (Für Version 1 reicht es, die Ergebnisse der Streams in markierten Blöcken an den Prompt zu geben).
|
||||
|
||||
### Teil C: The Synthesis (Prompting)
|
||||
Passe das Prompt-Template an, um die getrennten Streams zu nutzen:
|
||||
|
||||
...
|
||||
HIER SIND DIE FAKTEN:
|
||||
{stream_results_facts}
|
||||
|
||||
HIER SIND DIE RELEVANTEN WERTE/PRINZIPIEN:
|
||||
{stream_results_values}
|
||||
|
||||
HIER SIND DIE RISIKEN:
|
||||
{stream_results_risks}
|
||||
|
||||
AUFGABE: Wäge die Fakten gegen die Werte ab.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Deine Aufgaben:**
|
||||
1. **Refactor Config:** Zeige, wie die `decision_engine.yaml` für Multi-Stream angepasst wird.
|
||||
2. **Orchestrator Logic:** Schreibe die Python-Logik, die diese Streams parallel abfeuert und die Ergebnisse aggregiert.
|
||||
3. **Integration:** Nutze die `EdgeRegistry` (aus WP-22) für die Boosts in jedem Stream.
|
||||
|
||||
Bitte bestätige die Übernahme dieser "Agentic Architecture" (WP-25).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,123 +0,0 @@
|
|||
# Branch Merge Commit Message: WP15c
|
||||
|
||||
```
|
||||
feat: Multigraph-Support, Diversity Engine & Provenance Firewall (v2.9.1)
|
||||
|
||||
## Graph Topology & Edge Management (WP-15c)
|
||||
|
||||
### Section-Präzision & Multigraph-Modus
|
||||
- Präzise Erkennung von Obsidian-Ankern (`[[Note#Section]]`) und Self-Links (`[[#Section]]`)
|
||||
- Links werden in `target_id="Note"` und `target_section="Section"` aufgelöst
|
||||
- Edge-ID enthält `variant` (Section) für eindeutige Identifikation
|
||||
- Multigraph-Modus: Mehrere Kanten zwischen denselben Notizen möglich, wenn sie auf verschiedene Sections zeigen
|
||||
- Behebt "Phantom-Knoten" durch korrekte Trennung von Note-Name und Abschnitt
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/core/graph/graph_utils.py`: `parse_link_target()` für Section-Extraktion & Self-Links (v1.1.2)
|
||||
- `app/core/graph/graph_derive_edges.py`: Multigraph-Support, `target_section` in Edge-Payload (v1.1.2)
|
||||
- `app/core/graph/graph_subgraph.py`: Metadaten-Persistenz (v1.2.0)
|
||||
- `app/core/graph/graph_db_adapter.py`: Vollständige Metadaten-Durchreichung (v1.1.1)
|
||||
|
||||
### Provenance Firewall (Edge Registry v0.8.0)
|
||||
- System-Kanten (`next`, `prev`, `belongs_to`) dürfen nur mit `provenance="structure"` gesetzt werden
|
||||
- Alle anderen Provenienzen (`explicit`, `semantic_ai`, `inherited`, `global_pool`, `rule`) werden blockiert
|
||||
- Automatischer Fallback auf `related_to` bei unerlaubter Verwendung
|
||||
- Logging in `data/logs/unknown_edges.jsonl` für Admin-Review
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/services/edge_registry.py`: Provenance Firewall (v0.8.0)
|
||||
|
||||
## Retrieval-Intelligenz (The Diversity Engine)
|
||||
|
||||
### Note-Level Diversity Pooling
|
||||
- Pro `note_id` wird nur der relevanteste Treffer im Endergebnis behalten
|
||||
- Verhindert "Note-Flooding" (Dominanz einer einzigen Notiz durch viele ähnliche Chunks)
|
||||
- Workflow: Sortierung nach finalem Score → Diversity-Pooling → Begrenzung auf `top_k`
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/core/retrieval/retriever.py`: Note-Level Diversity Pooling (v0.7.0)
|
||||
|
||||
### Super-Edge Aggregation
|
||||
- Parallele Kanten zwischen zwei Notizen werden mathematisch zu einer "Super-Edge" aggregiert
|
||||
- Primäre Kante zählt voll, weitere Kanten werden mit Dämpfungsfaktor **0.1** gewichtet
|
||||
- Verhindert Score-Explosionen durch multiple Links auf verschiedene Sections
|
||||
- Metadaten: `is_super_edge` Flag und `edge_count` für Explanation Layer
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/core/retrieval/retriever.py`: Super-Edge Aggregation (v0.7.0)
|
||||
|
||||
### Metadaten-Persistenz
|
||||
- In-Memory Subgraph und Datenbank-Adapter erweitert für durchgängige Metadaten-Erhaltung
|
||||
- `target_section`, `provenance` und `confidence` werden vollständig erhalten
|
||||
- Ermöglicht präzises Retrieval-Reasoning und Explanation Layer
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/core/graph/graph_subgraph.py`: Metadaten-Persistenz (v1.2.0)
|
||||
- `app/core/graph/graph_db_adapter.py`: Vollständige Metadaten-Durchreichung (v1.1.1)
|
||||
|
||||
## Mathematisches Scoring (WP-22 Integration)
|
||||
|
||||
### Hybrid Scoring Formula (v1.0.3)
|
||||
- Neue Formel: `Final = (Semantic * StatusMult) * (1 + TypeBoost + EdgeBonus + CentBonus)`
|
||||
- Status-Gatekeeper: `stable` = 1.2, `draft` = 0.5, `active` = 1.0
|
||||
- Graph Boost Factor: Intent-spezifische Verstärkung (1.5x bei aktivem Intent)
|
||||
- Status wirkt als Multiplikator auf die Basis-Relevanz, Graph-Boni werden proportional verstärkt
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/core/retrieval/retriever_scoring.py`: Hybrid Scoring Formula (v1.0.3)
|
||||
|
||||
### Explanation Layer
|
||||
- Detaillierte Begründungen für jeden Bonus (Sektions-Links, Hub-Zentralität, Super-Edge-Informationen)
|
||||
- Provenance-Informationen für erhöhte Transparenz
|
||||
- Massiv erhöhte Transparenz für Debugging und Nutzer-Vertrauen
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/core/retrieval/retriever.py`: Explanation Layer (v0.7.0)
|
||||
|
||||
## Ingestion & Profil-Synchronisation
|
||||
|
||||
### Registry-First Profiling (v2.13.12)
|
||||
- Hierarchische Auflösung: Frontmatter > types.yaml Typ-Config > Global Defaults
|
||||
- Konsistente Verarbeitung je nach Notiz-Typ
|
||||
- Note-Typen wie `value` erhalten automatisch das korrekte Profil (`structured_smart_edges_strict`)
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/core/ingestion/ingestion_processor.py`: Registry-First Profiling (v2.13.12)
|
||||
|
||||
## Impact & Breaking Changes
|
||||
|
||||
### Keine Migration erforderlich
|
||||
**WICHTIG:** Diese Version ist **rückwärtskompatibel**. Bestehende Vaults funktionieren ohne Re-Import.
|
||||
|
||||
**Empfehlung:** Optionaler Re-Import für optimale Nutzung der neuen Features:
|
||||
```bash
|
||||
python3 -m scripts.import_markdown --vault ./vault --prefix "mindnet" --apply --force
|
||||
```
|
||||
|
||||
### Fixes
|
||||
- ✅ Resolves: "Phantom-Knoten" durch korrekte Trennung von Note-Name und Section
|
||||
- ✅ Resolves: Score-Explosionen durch multiple Links auf verschiedene Sections
|
||||
- ✅ Resolves: "Note-Flooding" durch fehlende Diversity-Filterung
|
||||
- ✅ Resolves: Inkonsistente Metadaten durch fehlende Persistenz im Subgraph
|
||||
- ✅ Resolves: Manipulation von System-Kanten durch Provenance Firewall
|
||||
|
||||
## Dokumentation
|
||||
|
||||
Alle relevanten Dokumente aktualisiert:
|
||||
- `02_concept_graph_logic.md`: Provenance Firewall, Self-Links, Multigraph-Support
|
||||
- `03_tech_retrieval_scoring.md`: Hybrid Scoring Formula, Note-Level Diversity, Super-Edge Aggregation
|
||||
- `03_tech_data_model.md`: Section-Support, Metadaten-Persistenz
|
||||
- `03_tech_ingestion_pipeline.md`: Registry-First Profiling (bereits dokumentiert)
|
||||
|
||||
## Versionen
|
||||
|
||||
- Edge Registry: v0.8.0
|
||||
- Graph Utils / Derive Edges: v1.1.2
|
||||
- Graph Subgraph: v1.2.0
|
||||
- Graph DB Adapter: v1.1.1
|
||||
- Retriever: v0.7.0
|
||||
- Retriever Scoring: v1.0.3
|
||||
- Ingestion Processor: v2.13.12
|
||||
|
||||
Closes #[issue-number]
|
||||
```
|
||||
|
|
@ -1,267 +0,0 @@
|
|||
# Release Notes: Mindnet v2.9.3 (WP15c)
|
||||
|
||||
**Release Date:** 2025-12-31
|
||||
**Type:** Feature Release - Multigraph & Diversity Engine
|
||||
**Branch:** WP15c
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Übersicht
|
||||
|
||||
Diese Version transformiert den Graphen von einer flachen Struktur zu einem **hochpräzisen Multigraphen** mit intelligenter Ergebnis-Filterung (Diversity) und gewichtetem Scoring. Die Änderungen verbessern die Retrieval-Qualität durch präzise Sektions-Links, Note-Level Diversity Pooling und mathematische Super-Edge Aggregation.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Neue Features
|
||||
|
||||
### Section-Präzision & Multigraph-Modus
|
||||
|
||||
Mindnet erkennt nun präzise **Obsidian-Anker** (`[[Note#Section]]`) und **Self-Links** (`[[#Section]]`):
|
||||
|
||||
**Obsidian-Anker:**
|
||||
```markdown
|
||||
[[rel:based_on Mein Leitbild#P3 – Disziplin]]
|
||||
```
|
||||
|
||||
**Self-Links:**
|
||||
```markdown
|
||||
[[#P3 – Disziplin]] → Verlinkt innerhalb derselben Note
|
||||
```
|
||||
|
||||
**Technische Details:**
|
||||
- Links werden in `target_id="Note"` und `target_section="Section"` aufgelöst
|
||||
- Edge-IDs enthalten `variant` (Section) für eindeutige Identifikation
|
||||
- **Multigraph-Modus:** Mehrere Kanten zwischen denselben Notizen möglich, wenn sie auf verschiedene Sections zeigen
|
||||
- Verhindert "Phantom-Knoten" durch korrekte Trennung von Note-Name und Abschnitt
|
||||
|
||||
### Provenance Firewall (Edge Registry v0.8.0)
|
||||
|
||||
Die **Edge Registry** stellt sicher, dass System-Kanten (`next`, `prev`, `belongs_to`) ausschließlich durch interne Struktur-Prozesse und nicht durch Nutzer oder KI manipuliert werden können:
|
||||
|
||||
**Schutz-Mechanismus:**
|
||||
- System-Kanten dürfen nur mit `provenance="structure"` gesetzt werden
|
||||
- Alle anderen Provenienzen (`explicit`, `semantic_ai`, `inherited`, `global_pool`, `rule`) werden blockiert
|
||||
- Automatischer Fallback auf `related_to` bei unerlaubter Verwendung
|
||||
- Logging in `data/logs/unknown_edges.jsonl` für Admin-Review
|
||||
|
||||
**Vorteil:** Garantiert Graph-Integrität und verhindert Manipulationen an der internen Struktur.
|
||||
|
||||
### Note-Level Diversity Pooling
|
||||
|
||||
Um das **"Note-Flooding"** (Dominanz einer einzigen Notiz durch viele ähnliche Chunks) zu verhindern, wird pro `note_id` nur noch der relevanteste Treffer im Endergebnis behalten:
|
||||
|
||||
**Workflow:**
|
||||
1. Alle Kandidaten werden nach finalem Score sortiert
|
||||
2. Pro `note_id` wird nur der Hit mit dem höchsten `total_score` behalten
|
||||
3. Begrenzung auf `top_k` nach dem Diversity-Pooling
|
||||
|
||||
**Impact:** Verhindert, dass 10 Chunks derselben Note andere KeyNotes verdrängen. Erhöht die Vielfalt der Suchergebnisse.
|
||||
|
||||
### Super-Edge Aggregation
|
||||
|
||||
Parallele Kanten zwischen zwei Notizen werden mathematisch zu einer **"Super-Edge"** aggregiert:
|
||||
|
||||
**Aggregations-Logik:**
|
||||
- Primäre Kante (höchstes Gewicht) zählt voll
|
||||
- Jede weitere Kante (z.B. auf eine andere Sektion) wird mit Dämpfungsfaktor **0.1** gewichtet
|
||||
- Formel: `total_weight = primary_weight + sum(secondary_weights * 0.1)`
|
||||
|
||||
**Vorteil:** Verhindert Score-Explosionen durch multiple Links auf verschiedene Sections derselben Note.
|
||||
|
||||
**Metadaten:**
|
||||
- `is_super_edge`: Flag für Explanation Layer
|
||||
- `edge_count`: Anzahl der aggregierten Kanten
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Verbesserungen
|
||||
|
||||
### Mathematisches Scoring (WP-22 Integration)
|
||||
|
||||
Die Engine berechnet den finalen Score basierend auf einer **hybriden Multiplikations-Formel**:
|
||||
|
||||
```
|
||||
Final = (Semantic * StatusMult) * (1 + TypeBoost + EdgeBonus + CentBonus)
|
||||
```
|
||||
|
||||
**Komponenten:**
|
||||
- **Base Score:** `Semantic * StatusMult` (Lifecycle-Filter)
|
||||
- **Status-Gatekeeper:** `stable` = 1.2, `draft` = 0.5, `active` = 1.0
|
||||
- **Boosts:** `TypeBoost + EdgeBonus + CentBonus` (additiv, dann multiplikativ auf Base)
|
||||
- **Graph Boost Factor:** Intent-spezifische Verstärkung (1.5x bei aktivem Intent)
|
||||
|
||||
**Vorteil:** Status wirkt als Multiplikator auf die Basis-Relevanz, Graph-Boni werden proportional verstärkt.
|
||||
|
||||
### Explanation Layer
|
||||
|
||||
Der Retriever liefert detaillierte Begründungen für jeden Bonus:
|
||||
- Sektions-Links (z.B. "Link zu 'Mein Leitbild#P3 – Disziplin'")
|
||||
- Hub-Zentralität (z.B. "Note ist zentraler Knoten mit 5 eingehenden Kanten")
|
||||
- Super-Edge-Informationen (z.B. "3 parallele Kanten aggregiert")
|
||||
- Provenance-Informationen (z.B. "Explizite Kante vom Nutzer")
|
||||
|
||||
**Impact:** Massiv erhöhte Transparenz für Debugging und Nutzer-Vertrauen.
|
||||
|
||||
### Metadaten-Persistenz
|
||||
|
||||
Der In-Memory Subgraph und der Datenbank-Adapter wurden so erweitert, dass Metadaten durchgängig erhalten bleiben:
|
||||
|
||||
**Erhaltene Metadaten:**
|
||||
- `target_section`: Abschnitts-Name für präzise Verlinkung
|
||||
- `provenance`: Herkunft der Kante (explicit, smart, rule, structure)
|
||||
- `confidence`: Vertrauenswürdigkeit (0.0 - 1.0)
|
||||
- `is_super_edge`: Flag für aggregierte Kanten
|
||||
|
||||
**Vorteil:** Ermöglicht präzises Retrieval-Reasoning und Explanation Layer.
|
||||
|
||||
### Profil-Synchronisation (Ingestion v2.13.12)
|
||||
|
||||
Der Ingestion-Prozessor löst Chunking-Profile hierarchisch über die `types.yaml` auf:
|
||||
|
||||
**Hierarchie:**
|
||||
1. **Frontmatter** (höchste Priorität)
|
||||
2. **types.yaml Typ-Config**
|
||||
3. **Global Defaults**
|
||||
|
||||
**Impact:** Konsistente Verarbeitung je nach Notiz-Typ. Note-Typen wie `value` erhalten automatisch das korrekte Profil (`structured_smart_edges_strict`).
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Bugfixes
|
||||
|
||||
- ✅ **Behoben:** "Phantom-Knoten" durch korrekte Trennung von Note-Name und Section in `target_id`
|
||||
- ✅ **Behoben:** Score-Explosionen durch multiple Links auf verschiedene Sections
|
||||
- ✅ **Behoben:** "Note-Flooding" durch fehlende Diversity-Filterung
|
||||
- ✅ **Behoben:** Inkonsistente Metadaten durch fehlende Persistenz im Subgraph
|
||||
- ✅ **Behoben:** Manipulation von System-Kanten durch Provenance Firewall
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Breaking Changes & Migration
|
||||
|
||||
### Keine Migration erforderlich
|
||||
|
||||
**WICHTIG:** Diese Version ist **rückwärtskompatibel**. Bestehende Vaults funktionieren ohne Re-Import.
|
||||
|
||||
**Empfehlung:** Optionaler Re-Import für optimale Nutzung der neuen Features:
|
||||
```bash
|
||||
python3 -m scripts.import_markdown --vault ./vault --prefix "mindnet" --apply --force
|
||||
```
|
||||
|
||||
**Was passiert beim Re-Import?**
|
||||
- Bestehende Links werden neu geparst und in `target_id` + `target_section` aufgeteilt
|
||||
- Self-Links (`[[#Section]]`) werden korrekt aufgelöst
|
||||
- Edge-Struktur wird konsolidiert (Multigraph-Support)
|
||||
|
||||
---
|
||||
|
||||
## 📚 API-Änderungen
|
||||
|
||||
### Keine Breaking Changes
|
||||
|
||||
Die API bleibt vollständig kompatibel. Neue Metadaten werden optional zurückgegeben:
|
||||
|
||||
**EdgeDTO (erweitert):**
|
||||
```python
|
||||
class EdgeDTO(BaseModel):
|
||||
# ... bestehende Felder ...
|
||||
target_section: Optional[str] = None # Neu: Abschnitts-Name
|
||||
is_super_edge: Optional[bool] = False # Neu: Aggregations-Flag
|
||||
```
|
||||
|
||||
**QueryResponse (erweitert):**
|
||||
- Explanation Layer enthält nun Super-Edge-Informationen
|
||||
- Metadaten wie `target_section` werden in Edge-Objekten zurückgegeben
|
||||
|
||||
---
|
||||
|
||||
## 📖 Dokumentation
|
||||
|
||||
Alle relevanten Dokumente wurden aktualisiert:
|
||||
|
||||
- ✅ `02_concept_graph_logic.md`: Provenance Firewall, Self-Links, Multigraph-Support
|
||||
- ✅ `03_tech_retrieval_scoring.md`: Hybrid Scoring Formula, Note-Level Diversity, Super-Edge Aggregation
|
||||
- ✅ `03_tech_data_model.md`: Section-Support, Metadaten-Persistenz
|
||||
- ✅ `03_tech_ingestion_pipeline.md`: Registry-First Profiling (bereits dokumentiert)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Technische Details
|
||||
|
||||
### Geänderte Module
|
||||
|
||||
**Graph Topology:**
|
||||
- `app/services/edge_registry.py`: Provenance Firewall (v0.8.0)
|
||||
- `app/core/graph/graph_utils.py`: `parse_link_target()` für Section-Extraktion & Self-Links (v1.1.2)
|
||||
- `app/core/graph/graph_derive_edges.py`: Multigraph-Support, `target_section` in Edge-Payload (v1.1.2)
|
||||
- `app/core/graph/graph_subgraph.py`: Metadaten-Persistenz (v1.2.0)
|
||||
- `app/core/graph/graph_db_adapter.py`: Vollständige Metadaten-Durchreichung (v1.1.1)
|
||||
|
||||
**Retrieval:**
|
||||
- `app/core/retrieval/retriever.py`: Note-Level Diversity Pooling, Super-Edge Aggregation (v0.7.0)
|
||||
- `app/core/retrieval/retriever_scoring.py`: Hybrid Scoring Formula (v1.0.3)
|
||||
|
||||
**Ingestion:**
|
||||
- `app/core/ingestion/ingestion_processor.py`: Registry-First Profiling (v2.13.12)
|
||||
|
||||
### Versionsnummern
|
||||
|
||||
- Edge Registry: **v0.8.0**
|
||||
- Graph Utils / Derive Edges: **v1.1.2**
|
||||
- Graph Subgraph: **v1.2.0**
|
||||
- Graph DB Adapter: **v1.1.1**
|
||||
- Retriever: **v0.7.0**
|
||||
- Retriever Scoring: **v1.0.3**
|
||||
- Ingestion Processor: **v2.13.12**
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Upgrade-Pfad
|
||||
|
||||
### Für Administratoren
|
||||
|
||||
1. **Code aktualisieren:**
|
||||
```bash
|
||||
git pull origin main
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **Services neu starten:**
|
||||
```bash
|
||||
sudo systemctl restart mindnet-prod
|
||||
sudo systemctl restart mindnet-ui-prod
|
||||
```
|
||||
|
||||
3. **Optional: Re-Import für optimale Nutzung:**
|
||||
```bash
|
||||
python3 -m scripts.import_markdown --vault ./vault --prefix "mindnet" --apply --force
|
||||
```
|
||||
|
||||
### Für Entwickler
|
||||
|
||||
- Keine Code-Änderungen erforderlich, wenn nur API genutzt wird
|
||||
- Frontend kann neue Metadaten (`target_section`, `is_super_edge`) optional nutzen
|
||||
- Explanation Layer enthält nun Super-Edge-Informationen
|
||||
|
||||
---
|
||||
|
||||
## 📝 Bekannte Einschränkungen
|
||||
|
||||
- **Re-Import-Dauer:** Große Vaults (>10.000 Notizen) können 30+ Minuten benötigen (optional)
|
||||
- **Temporärer Speicher:** Während des Re-Imports kann Qdrant-Speicher temporär ansteigen
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Danksagungen
|
||||
|
||||
Diese Version wurde durch umfangreiche Code-Analyse und Dokumentationsprüfung ermöglicht. Besonderer Fokus lag auf:
|
||||
- Transformation zu einem hochpräzisen Multigraphen
|
||||
- Intelligente Ergebnis-Filterung (Diversity Engine)
|
||||
- Mathematisches Scoring mit gewichteten Boni
|
||||
- Graph-Integrität durch Provenance Firewall
|
||||
|
||||
---
|
||||
|
||||
**Vollständige Changelog:** Siehe Git-Commits für detaillierte Änderungen
|
||||
**Support:** Bei Fragen siehe [Admin Operations Guide](../04_Operations/04_admin_operations.md)
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
# Branch Merge Commit: WP-24c
|
||||
|
||||
**Branch:** `WP24c`
|
||||
**Target:** `main`
|
||||
**Version:** v4.5.8
|
||||
**Date:** 2026-01-XX
|
||||
|
||||
---
|
||||
|
||||
## Commit Message
|
||||
|
||||
```
|
||||
feat: Phase 3 Agentic Edge Validation & Chunk-Aware Multigraph-System (v4.5.8)
|
||||
|
||||
### Phase 3 Agentic Edge Validation
|
||||
- Finales Validierungs-Gate für Kanten mit candidate: Präfix
|
||||
- LLM-basierte semantische Prüfung gegen Kontext (Note-Scope vs. Chunk-Scope)
|
||||
- Differenzierte Fehlerbehandlung: Transiente Fehler erlauben Kante, permanente Fehler lehnen ab
|
||||
- Kontext-Optimierung: Note-Scope nutzt Note-Summary/Text, Chunk-Scope nutzt spezifischen Chunk-Text
|
||||
- Implementierung in app/core/ingestion/ingestion_validation.py (v2.14.0)
|
||||
|
||||
### Automatische Spiegelkanten (Invers-Logik)
|
||||
- Automatische Erzeugung von Spiegelkanten für explizite Verbindungen
|
||||
- Phase 2 Batch-Injektion am Ende des Imports
|
||||
- Authority-Check: Explizite Kanten haben Vorrang (keine Duplikate)
|
||||
- Provenance Firewall: System-Kanten können nicht manuell überschrieben werden
|
||||
- Implementierung in app/core/ingestion/ingestion_processor.py (v2.13.12)
|
||||
|
||||
### Note-Scope Zonen (v4.2.0)
|
||||
- Globale Verbindungen für ganze Notizen (scope: note)
|
||||
- Konfigurierbare Header-Namen via ENV-Variablen
|
||||
- Höchste Priorität bei Duplikaten
|
||||
- Phase 3 Validierung nutzt Note-Summary/Text für bessere Präzision
|
||||
- Implementierung in app/core/graph/graph_derive_edges.py (v1.1.2)
|
||||
|
||||
### Chunk-Aware Multigraph-System
|
||||
- Section-basierte Links: [[Note#Section]] wird präzise in target_id und target_section aufgeteilt
|
||||
- Multigraph-Support: Mehrere Kanten zwischen denselben Knoten möglich (verschiedene Sections)
|
||||
- Semantische Deduplizierung basierend auf src->tgt:kind@sec Key
|
||||
- Metadaten-Persistenz: target_section, provenance, confidence bleiben erhalten
|
||||
|
||||
### Code-Komponenten
|
||||
- app/core/ingestion/ingestion_validation.py: v2.14.0 (Phase 3 Validierung, Kontext-Optimierung)
|
||||
- app/core/ingestion/ingestion_processor.py: v2.13.12 (Automatische Spiegelkanten, Authority-Check)
|
||||
- app/core/graph/graph_derive_edges.py: v1.1.2 (Note-Scope Zonen, LLM-Validierung Zonen)
|
||||
- app/core/chunking/chunking_processor.py: v2.13.0 (LLM-Validierung Zonen Erkennung)
|
||||
- app/core/chunking/chunking_parser.py: v2.12.0 (Header-Level Erkennung, Zonen-Extraktion)
|
||||
|
||||
### Konfiguration
|
||||
- Neue ENV-Variablen für konfigurierbare Header:
|
||||
- MINDNET_LLM_VALIDATION_HEADERS (Default: "Unzugeordnete Kanten,Edge Pool,Candidates")
|
||||
- MINDNET_LLM_VALIDATION_HEADER_LEVEL (Default: 3)
|
||||
- MINDNET_NOTE_SCOPE_ZONE_HEADERS (Default: "Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen")
|
||||
- MINDNET_NOTE_SCOPE_HEADER_LEVEL (Default: 2)
|
||||
- config/llm_profiles.yaml: ingest_validator Profil für Phase 3 Validierung (Temperature 0.0)
|
||||
- config/prompts.yaml: edge_validation Prompt für Phase 3 Validierung
|
||||
|
||||
### Dokumentation
|
||||
- 01_knowledge_design.md: Automatische Spiegelkanten, Phase 3 Validierung, Note-Scope Zonen
|
||||
- NOTE_SCOPE_ZONEN.md: Phase 3 Validierung integriert
|
||||
- LLM_VALIDIERUNG_VON_LINKS.md: Phase 3 statt global_pool, Kontext-Optimierung
|
||||
- 02_concept_graph_logic.md: Phase 3 Validierung, automatische Spiegelkanten, Note-Scope vs. Chunk-Scope
|
||||
- 03_tech_data_model.md: candidate: Präfix, verified Status, virtual Flag, scope Feld
|
||||
- 03_tech_configuration.md: Neue ENV-Variablen dokumentiert
|
||||
- 04_admin_operations.md: Troubleshooting für Phase 3 Validierung und Note-Scope Links
|
||||
- 05_testing_guide.md: WP-24c Test-Szenarien hinzugefügt
|
||||
- 00_quality_checklist.md: WP-24c Features in Checkliste aufgenommen
|
||||
- README.md: Version auf v4.5.8 aktualisiert, WP-24c Features verlinkt
|
||||
|
||||
### Breaking Changes
|
||||
- Keine Breaking Changes für Endbenutzer
|
||||
- Vollständige Rückwärtskompatibilität
|
||||
- Bestehende Notizen funktionieren ohne Änderungen
|
||||
|
||||
### Migration
|
||||
- Keine Migration erforderlich
|
||||
- System funktioniert ohne Änderungen
|
||||
- Optional: ENV-Variablen können für Custom-Header konfiguriert werden
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ WP-24c ist zu 100% implementiert und audit-geprüft.
|
||||
**Nächster Schritt:** WP-25c (Kontext-Budgeting & Erweiterte Prompt-Optimierung).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
Dieser Merge führt die **Phase 3 Agentic Edge Validation** und das **Chunk-Aware Multigraph-System** in MindNet ein. Das System validiert nun automatisch Kanten mit `candidate:` Präfix, erzeugt automatisch Spiegelkanten für explizite Verbindungen und unterstützt Note-Scope Zonen für globale Verbindungen.
|
||||
|
||||
**Kern-Features:**
|
||||
- Phase 3 Agentic Edge Validation (finales Validierungs-Gate)
|
||||
- Automatische Spiegelkanten (Invers-Logik)
|
||||
- Note-Scope Zonen (globale Verbindungen)
|
||||
- Chunk-Aware Multigraph-System (Section-basierte Links)
|
||||
|
||||
**Technische Integrität:**
|
||||
- Alle Kanten durchlaufen Phase 3 Validierung (falls candidate: Präfix)
|
||||
- Spiegelkanten werden automatisch erzeugt (Phase 2)
|
||||
- Note-Scope Links haben höchste Priorität
|
||||
- Kontext-Optimierung für bessere Validierungs-Genauigkeit
|
||||
|
||||
**Dokumentation:**
|
||||
- Vollständige Aktualisierung aller relevanten Dokumente
|
||||
- Neue ENV-Variablen dokumentiert
|
||||
- Troubleshooting-Guide erweitert
|
||||
- Test-Szenarien hinzugefügt
|
||||
|
||||
**Deployment:**
|
||||
- Keine Breaking Changes
|
||||
- Optional: ENV-Variablen für Custom-Header konfigurieren
|
||||
- System funktioniert ohne Änderungen
|
||||
|
|
@ -1,407 +0,0 @@
|
|||
# MindNet v4.5.8 - Release Notes: WP-24c
|
||||
|
||||
**Release Date:** 2026-01-XX
|
||||
**Type:** Feature Release - Phase 3 Agentic Edge Validation & Chunk-Aware Multigraph-System
|
||||
**Version:** 4.5.8 (WP-24c)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Überblick
|
||||
|
||||
Mit WP-24c wurde MindNet um ein **finales Validierungs-Gate (Phase 3 Agentic Edge Validation)** erweitert, das "Geister-Verknüpfungen" verhindert und die Graph-Qualität sichert. Zusätzlich wurde das System um **automatische Spiegelkanten (Invers-Logik)** und **Note-Scope Zonen** erweitert, die es ermöglichen, globale Verbindungen für ganze Notizen zu definieren.
|
||||
|
||||
Diese Version markiert einen wichtigen Schritt zur **Graph-Integrität**: Von manueller Kanten-Pflege hin zu automatischer Validierung und bidirektionaler Durchsuchbarkeit.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Neue Features
|
||||
|
||||
### 1. Phase 3 Agentic Edge Validation
|
||||
|
||||
**Implementierung (`app/core/ingestion/ingestion_validation.py` v2.14.0):**
|
||||
|
||||
Finales Validierungs-Gate für alle Kanten mit `candidate:` Präfix:
|
||||
|
||||
* **Trigger-Kriterium:** Kanten in `### Unzugeordnete Kanten` Sektionen erhalten `candidate:` Präfix
|
||||
* **Validierungsprozess:** LLM prüft semantisch, ob die Verbindung zum Kontext passt
|
||||
* **Ergebnis:** VERIFIED (Präfix entfernt, persistiert) oder REJECTED (nicht in DB geschrieben)
|
||||
* **Kontext-Optimierung:** Note-Scope nutzt Note-Summary/Text, Chunk-Scope nutzt spezifischen Chunk-Text
|
||||
|
||||
**Vorteile:**
|
||||
* **Graph-Qualität:** Verhindert persistente "Geister-Verknüpfungen"
|
||||
* **Präzision:** Höhere Validierungs-Genauigkeit durch Kontext-Optimierung
|
||||
* **Fehlertoleranz:** Unterscheidung zwischen transienten (Netzwerk) und permanenten (Config) Fehlern
|
||||
|
||||
### 2. Automatische Spiegelkanten (Invers-Logik)
|
||||
|
||||
**Implementierung (`app/core/ingestion/ingestion_processor.py` v2.13.12):**
|
||||
|
||||
Automatische Erzeugung von Spiegelkanten für explizite Verbindungen:
|
||||
|
||||
* **Funktionsweise:** Explizite Kante `A depends_on: B` erzeugt automatisch `B enforced_by: A`
|
||||
* **Priorität:** Explizite Kanten haben Vorrang (keine Duplikate)
|
||||
* **Schutz:** System-Kanten (`belongs_to`, `next`, `prev`) können nicht manuell überschrieben werden
|
||||
* **Phase 2 Injektion:** Spiegelkanten werden am Ende des Imports in einem Batch-Prozess injiziert
|
||||
|
||||
**Vorteile:**
|
||||
* **Bidirektionale Durchsuchbarkeit:** Beide Richtungen sind durchsuchbar ohne manuelle Pflege
|
||||
* **Konsistenz:** Volle Graph-Konsistenz ohne "Link-Nightmare"
|
||||
* **Höhere Wirksamkeit:** Explizite Kanten haben höhere Confidence-Werte als automatisch generierte
|
||||
|
||||
### 3. Note-Scope Zonen (v4.2.0)
|
||||
|
||||
**Implementierung (`app/core/graph/graph_derive_edges.py` v1.1.2):**
|
||||
|
||||
Globale Verbindungen für ganze Notizen:
|
||||
|
||||
* **Format:** Links in `## Smart Edges` Zonen werden als `scope: note` behandelt
|
||||
* **Priorität:** Höchste Priorität bei Duplikaten
|
||||
* **Phase 3 Validierung:** Nutzt Note-Summary (Top 5 Chunks) oder Note-Text für bessere Validierung
|
||||
* **Konfigurierbar:** Header-Namen und -Ebene via ENV-Variablen
|
||||
|
||||
**Vorteile:**
|
||||
* **Globale Verbindungen:** Links gelten für die gesamte Note, nicht nur einen Abschnitt
|
||||
* **Bessere Validierung:** Note-Kontext ermöglicht präzisere LLM-Validierung
|
||||
* **Flexibilität:** Konfigurierbare Header-Namen für verschiedene Workflows
|
||||
|
||||
### 4. Chunk-Aware Multigraph-System
|
||||
|
||||
**Erweiterung des bestehenden Multigraph-Systems:**
|
||||
|
||||
* **Section-basierte Links:** `[[Note#Section]]` wird präzise in `target_id` und `target_section` aufgeteilt
|
||||
* **Multigraph-Support:** Mehrere Kanten zwischen denselben Knoten möglich, wenn sie auf verschiedene Sections zeigen
|
||||
* **Semantische Deduplizierung:** Basierend auf `src->tgt:kind@sec` Key
|
||||
|
||||
**Vorteile:**
|
||||
* **Präzision:** Präzise Verlinkung innerhalb langer Dokumente
|
||||
* **Flexibilität:** Mehrere Verbindungen zur gleichen Note möglich
|
||||
* **Konsistenz:** Verhindert "Phantom-Knoten"
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Technische Änderungen
|
||||
|
||||
### Konfigurationsdateien
|
||||
|
||||
**`config/llm_profiles.yaml` (v1.3.0):**
|
||||
* **Keine Änderungen:** Bestehende Profile bleiben unverändert
|
||||
* **`ingest_validator` Profil:** Wird für Phase 3 Validierung genutzt (Temperature 0.0 für Determinismus)
|
||||
|
||||
**`config/prompts.yaml` (v3.2.2):**
|
||||
* **Keine Änderungen:** Bestehende Prompts bleiben unverändert
|
||||
* **`edge_validation` Prompt:** Wird für Phase 3 Validierung genutzt
|
||||
|
||||
### Environment Variablen (`.env`)
|
||||
|
||||
**Neue Variablen für WP-24c:**
|
||||
|
||||
```env
|
||||
# --- WP-24c v4.2.0: Konfigurierbare Markdown-Header für Edge-Zonen ---
|
||||
# Komma-separierte Liste von Headern für LLM-Validierung
|
||||
# Format: Header1,Header2,Header3
|
||||
MINDNET_LLM_VALIDATION_HEADERS=Unzugeordnete Kanten,Edge Pool,Candidates
|
||||
|
||||
# Header-Ebene für LLM-Validierung (1-6, Default: 3 für ###)
|
||||
MINDNET_LLM_VALIDATION_HEADER_LEVEL=3
|
||||
|
||||
# Komma-separierte Liste von Headern für Note-Scope Zonen
|
||||
# Format: Header1,Header2,Header3
|
||||
MINDNET_NOTE_SCOPE_ZONE_HEADERS=Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen
|
||||
|
||||
# Header-Ebene für Note-Scope Zonen (1-6, Default: 2 für ##)
|
||||
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
|
||||
```
|
||||
|
||||
**Default-Werte:**
|
||||
* `MINDNET_LLM_VALIDATION_HEADERS`: `Unzugeordnete Kanten,Edge Pool,Candidates`
|
||||
* `MINDNET_LLM_VALIDATION_HEADER_LEVEL`: `3` (für `###`)
|
||||
* `MINDNET_NOTE_SCOPE_ZONE_HEADERS`: `Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen`
|
||||
* `MINDNET_NOTE_SCOPE_HEADER_LEVEL`: `2` (für `##`)
|
||||
|
||||
**Hinweis:** Falls diese Variablen nicht gesetzt sind, werden die Default-Werte verwendet. Das System funktioniert ohne explizite Konfiguration.
|
||||
|
||||
### Code-Komponenten
|
||||
|
||||
**Neue/Erweiterte Module:**
|
||||
|
||||
* `app/core/ingestion/ingestion_validation.py`: v2.14.0
|
||||
* Phase 3 Validierung mit Kontext-Optimierung
|
||||
* Differenzierte Fehlerbehandlung (transient vs. permanent)
|
||||
* Lazy-Prompt-Orchestration Integration
|
||||
|
||||
* `app/core/ingestion/ingestion_processor.py`: v2.13.12
|
||||
* Automatische Spiegelkanten-Generierung (Phase 2)
|
||||
* Authority-Check für explizite Kanten
|
||||
* ID-Konsistenz mit Phase 1
|
||||
|
||||
* `app/core/graph/graph_derive_edges.py`: v1.1.2
|
||||
* Note-Scope Zonen Extraktion
|
||||
* LLM-Validierung Zonen Extraktion
|
||||
* Konfigurierbare Header-Erkennung
|
||||
|
||||
* `app/core/chunking/chunking_processor.py`: v2.13.0
|
||||
* LLM-Validierung Zonen Erkennung
|
||||
* candidate: Präfix-Setzung
|
||||
|
||||
* `app/core/chunking/chunking_parser.py`: v2.12.0
|
||||
* Header-Level Erkennung
|
||||
* Zonen-Extraktion
|
||||
|
||||
---
|
||||
|
||||
## 📋 Migration Guide
|
||||
|
||||
### Für Endbenutzer
|
||||
|
||||
**Keine Migration erforderlich!** Das System funktioniert ohne Änderungen.
|
||||
|
||||
**Optionale Nutzung neuer Features:**
|
||||
|
||||
1. **Explizite Links (empfohlen):**
|
||||
```markdown
|
||||
Diese Entscheidung [[rel:depends_on Performance-Analyse]] wurde getroffen.
|
||||
```
|
||||
* Sofortige Übernahme, höchste Priorität, keine Validierung
|
||||
|
||||
2. **Validierte Links (für explorative Verbindungen):**
|
||||
```markdown
|
||||
### Unzugeordnete Kanten
|
||||
|
||||
related_to:Mögliche Verbindung
|
||||
depends_on:Unsicherer Link
|
||||
```
|
||||
* Phase 3 Validierung, kann abgelehnt werden
|
||||
|
||||
3. **Note-Scope Links (für globale Verbindungen):**
|
||||
```markdown
|
||||
## Smart Edges
|
||||
|
||||
[[rel:depends_on|Projekt-Übersicht]]
|
||||
[[rel:part_of|Größeres System]]
|
||||
```
|
||||
* Globale Verbindung für ganze Note, höchste Priorität
|
||||
|
||||
### Für Administratoren
|
||||
|
||||
**1. Environment Variablen hinzufügen (optional):**
|
||||
|
||||
Fügen Sie die folgenden Zeilen zu Ihrer `.env` oder `config/prod.env` hinzu:
|
||||
|
||||
```env
|
||||
# --- WP-24c v4.2.0: Konfigurierbare Markdown-Header für Edge-Zonen ---
|
||||
MINDNET_LLM_VALIDATION_HEADERS=Unzugeordnete Kanten,Edge Pool,Candidates
|
||||
MINDNET_LLM_VALIDATION_HEADER_LEVEL=3
|
||||
MINDNET_NOTE_SCOPE_ZONE_HEADERS=Smart Edges,Relationen,Global Links,Note-Level Relations,Globale Verbindungen
|
||||
MINDNET_NOTE_SCOPE_HEADER_LEVEL=2
|
||||
```
|
||||
|
||||
**Hinweis:** Falls diese Variablen nicht gesetzt sind, werden die Default-Werte verwendet. Das System funktioniert ohne explizite Konfiguration.
|
||||
|
||||
**2. LLM-Profil prüfen:**
|
||||
|
||||
Stellen Sie sicher, dass das `ingest_validator` Profil in `config/llm_profiles.yaml` existiert:
|
||||
|
||||
```yaml
|
||||
ingest_validator:
|
||||
provider: ollama
|
||||
model: phi3:mini
|
||||
temperature: 0.0
|
||||
fallback_profile: null
|
||||
```
|
||||
|
||||
**3. Prompt prüfen:**
|
||||
|
||||
Stellen Sie sicher, dass der `edge_validation` Prompt in `config/prompts.yaml` existiert.
|
||||
|
||||
**4. System neu starten:**
|
||||
|
||||
Nach dem Hinzufügen der ENV-Variablen:
|
||||
|
||||
```bash
|
||||
systemctl restart mindnet-prod
|
||||
systemctl restart mindnet-ui-prod
|
||||
```
|
||||
|
||||
### Für Entwickler
|
||||
|
||||
**Keine Code-Änderungen erforderlich!** Die neuen Features sind vollständig rückwärtskompatibel.
|
||||
|
||||
**Optionale Integration:**
|
||||
|
||||
* **Phase 3 Validierung:** Nutzen Sie `validate_edge_candidate()` aus `ingestion_validation.py`
|
||||
* **Note-Scope Zonen:** Nutzen Sie `extract_note_scope_zones()` aus `graph_derive_edges.py`
|
||||
* **Spiegelkanten:** Werden automatisch erzeugt, keine manuelle Integration erforderlich
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment-Anweisungen
|
||||
|
||||
### Pre-Deployment Checkliste
|
||||
|
||||
- [ ] **Backup:** Vollständiges Backup von Qdrant und Vault durchführen
|
||||
- [ ] **ENV-Variablen:** Neue ENV-Variablen zu `.env` hinzufügen (optional)
|
||||
- [ ] **LLM-Profil:** `ingest_validator` Profil in `llm_profiles.yaml` prüfen
|
||||
- [ ] **Prompt:** `edge_validation` Prompt in `prompts.yaml` prüfen
|
||||
- [ ] **Dependencies:** `requirements.txt` aktualisieren (falls neue Abhängigkeiten)
|
||||
- [ ] **Tests:** Unit Tests und Integration Tests ausführen
|
||||
|
||||
### Deployment-Schritte
|
||||
|
||||
**1. Code aktualisieren:**
|
||||
|
||||
```bash
|
||||
git pull origin main
|
||||
# oder
|
||||
git checkout WP24c
|
||||
git merge main
|
||||
```
|
||||
|
||||
**2. Dependencies aktualisieren:**
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**3. ENV-Variablen konfigurieren (optional):**
|
||||
|
||||
```bash
|
||||
# Fügen Sie die neuen Variablen zu .env hinzu
|
||||
nano .env
|
||||
# oder
|
||||
nano config/prod.env
|
||||
```
|
||||
|
||||
**4. Services neu starten:**
|
||||
|
||||
```bash
|
||||
systemctl restart mindnet-prod
|
||||
systemctl restart mindnet-ui-prod
|
||||
```
|
||||
|
||||
**5. Health Check:**
|
||||
|
||||
```bash
|
||||
curl http://localhost:8001/healthz
|
||||
curl http://localhost:8501/healthz
|
||||
```
|
||||
|
||||
**6. Logs prüfen:**
|
||||
|
||||
```bash
|
||||
journalctl -u mindnet-prod -n 50 --no-pager
|
||||
journalctl -u mindnet-ui-prod -n 50 --no-pager
|
||||
```
|
||||
|
||||
### Post-Deployment Validierung
|
||||
|
||||
**1. Phase 3 Validierung testen:**
|
||||
|
||||
Erstellen Sie eine Test-Notiz mit `### Unzugeordnete Kanten`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
type: concept
|
||||
title: Test-Notiz
|
||||
---
|
||||
|
||||
# Test-Notiz
|
||||
|
||||
Hier ist der Inhalt...
|
||||
|
||||
### Unzugeordnete Kanten
|
||||
|
||||
related_to:Test-Ziel
|
||||
```
|
||||
|
||||
**Erwartetes Verhalten:**
|
||||
* Log zeigt `🚀 [PHASE 3] Validierung: ...`
|
||||
* Log zeigt `✅ [PHASE 3] VERIFIED:` oder `🚫 [PHASE 3] REJECTED:`
|
||||
* Kante wird nur bei VERIFIED persistiert
|
||||
|
||||
**2. Note-Scope Zonen testen:**
|
||||
|
||||
Erstellen Sie eine Test-Notiz mit `## Smart Edges`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
type: decision
|
||||
title: Test-Entscheidung
|
||||
---
|
||||
|
||||
# Test-Entscheidung
|
||||
|
||||
Hier ist der Inhalt...
|
||||
|
||||
## Smart Edges
|
||||
|
||||
[[rel:depends_on|Test-Projekt]]
|
||||
```
|
||||
|
||||
**Erwartetes Verhalten:**
|
||||
* Link wird als `scope: note` behandelt
|
||||
* `provenance: explicit:note_zone`
|
||||
* Höchste Priorität bei Duplikaten
|
||||
|
||||
**3. Automatische Spiegelkanten testen:**
|
||||
|
||||
Erstellen Sie eine explizite Kante:
|
||||
|
||||
```markdown
|
||||
[[rel:depends_on Projekt Alpha]]
|
||||
```
|
||||
|
||||
**Erwartetes Verhalten:**
|
||||
* Log zeigt `🔄 [SYMMETRY] Add inverse: ...`
|
||||
* Beide Richtungen sind durchsuchbar
|
||||
* Explizite Kante hat höhere Priorität
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Bekannte Probleme & Einschränkungen
|
||||
|
||||
**Keine bekannten Probleme.**
|
||||
|
||||
**Hinweise:**
|
||||
|
||||
* **Phase 3 Validierung:** Erfordert LLM-Verfügbarkeit. Bei transienten Fehlern wird die Kante erlaubt (Datenintegrität vor Präzision).
|
||||
* **Spiegelkanten:** Werden nur für explizite Kanten erzeugt. Validierte Kanten erhalten keine Spiegelkanten, bis sie VERIFIED sind.
|
||||
* **Note-Scope:** Header-Namen müssen exakt (case-insensitive) übereinstimmen.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Dokumentation
|
||||
|
||||
**Aktualisierte Dokumente:**
|
||||
|
||||
* `docs/01_User_Manual/01_knowledge_design.md` - Automatische Spiegelkanten, Phase 3 Validierung, Note-Scope Zonen
|
||||
* `docs/01_User_Manual/NOTE_SCOPE_ZONEN.md` - Phase 3 Validierung integriert
|
||||
* `docs/01_User_Manual/LLM_VALIDIERUNG_VON_LINKS.md` - Phase 3 statt global_pool
|
||||
* `docs/02_concepts/02_concept_graph_logic.md` - Phase 3 Validierung, automatische Spiegelkanten, Note-Scope vs. Chunk-Scope
|
||||
* `docs/03_Technical_References/03_tech_data_model.md` - candidate: Präfix, verified Status, virtual Flag
|
||||
* `docs/03_Technical_References/03_tech_configuration.md` - Neue ENV-Variablen dokumentiert
|
||||
* `docs/04_Operations/04_admin_operations.md` - Troubleshooting für Phase 3 Validierung
|
||||
* `docs/05_Development/05_testing_guide.md` - WP-24c Test-Szenarien
|
||||
|
||||
**Neue Dokumente:**
|
||||
|
||||
* Keine neuen Dokumente (alle Features in bestehenden Dokumenten integriert)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Breaking Changes
|
||||
|
||||
**Keine Breaking Changes!**
|
||||
|
||||
Das System ist vollständig rückwärtskompatibel. Bestehende Notizen funktionieren ohne Änderungen.
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Danksagungen
|
||||
|
||||
Diese Version wurde entwickelt, um die Graph-Integrität zu sichern und die Benutzerfreundlichkeit durch automatische Spiegelkanten zu verbessern.
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ WP-24c ist zu 100% implementiert und audit-geprüft.
|
||||
**Nächster Schritt:** WP-25c (Kontext-Budgeting & Erweiterte Prompt-Optimierung).
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
# Branch Merge Commit Message: WP25
|
||||
|
||||
```
|
||||
feat: Agentic Multi-Stream RAG Orchestration (v3.0)
|
||||
|
||||
## Architektur-Transformation (WP-25)
|
||||
|
||||
### Agentic Multi-Stream RAG Orchestration
|
||||
- Übergang von linearer RAG-Architektur zu paralleler Multi-Stream Engine
|
||||
- Parallele Abfragen in spezialisierten Wissens-Streams (Values, Facts, Biography, Risk, Tech)
|
||||
- Stream-Tracing: Jeder Treffer wird mit `stream_origin` markiert
|
||||
- Fehler-Resilienz: Einzelne Stream-Fehler blockieren nicht die gesamte Anfrage
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/core/retrieval/decision_engine.py`: Multi-Stream Orchestrator (v1.0.3)
|
||||
- `app/routers/chat.py`: Hybrid Router Integration (v3.0.2)
|
||||
- `app/models/dto.py`: Stream-Tracing Support (v0.7.1)
|
||||
|
||||
### Intent-basiertes Routing ("The Brain")
|
||||
- Hybrid-Modus: Keyword Fast-Path + LLM Slow-Path
|
||||
- Strategien: FACT_WHAT, FACT_WHEN, DECISION, EMPATHY, CODING, INTERVIEW
|
||||
- Sofortige Erkennung von Triggern wie "Soll ich" oder "Wann" ohne LLM-Call
|
||||
- Komplexe semantische Analyse für unklare Anfragen
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/routers/chat.py`: Hybrid Router mit Keyword-Fast-Path (v3.0.2)
|
||||
- `app/core/retrieval/decision_engine.py`: LLM-basiertes Routing (v1.0.3)
|
||||
|
||||
### Wissens-Synthese
|
||||
- Spezialisierte Templates in `prompts.yaml` mit expliziten Stream-Variablen
|
||||
- Pre-Initialization aller Stream-Variablen (verhindert KeyErrors)
|
||||
- Provider-spezifische Templates für Ollama, Gemini und OpenRouter
|
||||
- Differenzierte Abwägung zwischen Fakten und persönlichen Werten
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `config/prompts.yaml`: Stream-Templates (v3.1.2)
|
||||
- `app/core/retrieval/decision_engine.py`: Synthese-Logik (v1.0.3)
|
||||
|
||||
## Stream-Konfiguration & Typ-Synchronisation
|
||||
|
||||
### Stream-Library (decision_engine.yaml v3.1.6)
|
||||
- **Values Stream:** Identität, Ethik und Prinzipien (filter_types: value, principle, belief, etc.)
|
||||
- **Facts Stream:** Operative Daten (filter_types: project, decision, task, etc.)
|
||||
- **Biography Stream:** Persönliche Erfahrungen (filter_types: experience, journal, profile)
|
||||
- **Risk Stream:** Hindernisse und Gefahren (filter_types: risk, obstacle, bias)
|
||||
- **Tech Stream:** Technisches Wissen (filter_types: concept, source, glossary, etc.)
|
||||
|
||||
**Edge-Boosts pro Stream:**
|
||||
- Values: `guides: 3.0`, `enforced_by: 2.5`, `based_on: 2.0`
|
||||
- Facts: `part_of: 2.0`, `depends_on: 1.5`, `implemented_in: 1.5`
|
||||
- Risk: `blocks: 2.5`, `impacts: 2.0`, `risk_of: 2.5`
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `config/decision_engine.yaml`: Multi-Stream Konfiguration (v3.1.6)
|
||||
|
||||
## Bugfixes & Optimierungen
|
||||
|
||||
### Vault-Import Performance
|
||||
- "Empty Response Guard" korrigiert: Kurze Ingest-Antworten (YES/NO) lösen nicht mehr fälschlicherweise langsame Fallbacks aus
|
||||
- Entfernung des <5-Zeichen Guards ermöglicht YES/NO Validierungen
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/services/llm_service.py`: Ingest-Stability Patch (v3.4.2)
|
||||
|
||||
### Cloud-Resilienz
|
||||
- Sicherheitscheck für das `choices`-Array in `_execute_openrouter` implementiert
|
||||
- Verhindert JSON-Errors bei überlasteten APIs
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/services/llm_service.py`: Empty Response Guard (v3.4.2)
|
||||
|
||||
### Template-Robustheit
|
||||
- Automatische Vor-Initialisierung aller Stream-Variablen in der `DecisionEngine`
|
||||
- Verhindert `KeyError`-Abstürze bei unvollständigen Konfigurationen
|
||||
- Fallback-Mechanismus bei Template-Fehlern
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/core/retrieval/decision_engine.py`: Pre-Initialization (v1.0.3)
|
||||
|
||||
### Intent-Kollision
|
||||
- Generische Begriffe (wie "Projekt") wurden aus den Keyword-Triggern entfernt
|
||||
- Sicherstellt, dass strategische Fragen (z.B. "Soll ich...?") korrekt als `DECISION` geroutet werden
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `config/decision_engine.yaml`: Keyword-Fix (v3.1.6)
|
||||
|
||||
## Ollama Context-Throttling
|
||||
|
||||
- Vor der Übergabe an Ollama prüft der Chat-Router, ob der Kontext die Grenze von `MAX_OLLAMA_CHARS` überschreitet (Standard: 10.000)
|
||||
- Automatische Kürzung bei großen Kontexten
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/routers/chat.py`: Context-Throttling (v3.0.2)
|
||||
|
||||
## Lifespan Management
|
||||
|
||||
- FastAPI-Anwendung implementiert Lifespan-Management für sauberen Startup und Shutdown
|
||||
- Integritäts-Check der WP-25 Konfiguration beim Startup
|
||||
- Ressourcen-Cleanup beim Shutdown
|
||||
- Globale Fehlerbehandlung für asynchrone Prozesse
|
||||
|
||||
**Geänderte Dateien:**
|
||||
- `app/main.py`: Lifespan Management (v1.0.0)
|
||||
|
||||
## Impact & Breaking Changes
|
||||
|
||||
### Keine Migration erforderlich
|
||||
**WICHTIG:** Diese Version ist **rückwärtskompatibel**. Bestehende Vaults funktionieren ohne Re-Import.
|
||||
|
||||
**Konfigurations-Updates:**
|
||||
- `decision_engine.yaml` wurde auf v3.1.6 aktualisiert (Multi-Stream Struktur)
|
||||
- `prompts.yaml` wurde auf v3.1.2 aktualisiert (Stream-Templates)
|
||||
- **Empfehlung:** Backup der alten Konfigurationsdateien vor dem Update
|
||||
|
||||
### API-Erweiterungen
|
||||
- `QueryHit.stream_origin`: Name des Ursprungs-Streams (optional)
|
||||
- `ChatResponse.intent`: Die gewählte WP-25 Strategie (optional)
|
||||
- `ChatResponse.intent_source`: Quelle der Intent-Erkennung (optional)
|
||||
|
||||
## Dokumentation
|
||||
|
||||
Alle relevanten Dokumente aktualisiert:
|
||||
- `03_tech_chat_backend.md`: Agentic Multi-Stream RAG, Intent-basiertes Routing, Wissens-Synthese
|
||||
- `03_tech_configuration.md`: decision_engine.yaml, prompts.yaml (Stream-Struktur)
|
||||
- `03_tech_api_reference.md`: Erweiterte DTOs (stream_origin, intent)
|
||||
|
||||
## Versionen
|
||||
|
||||
- Decision Engine: v1.0.3
|
||||
- Chat Router: v3.0.2
|
||||
- LLM Service: v3.4.2
|
||||
- DTOs: v0.7.1
|
||||
- Main: v1.0.0
|
||||
- decision_engine.yaml: v3.1.6
|
||||
- prompts.yaml: v3.1.2
|
||||
|
||||
Closes #[issue-number]
|
||||
```
|
||||
|
|
@ -1,268 +0,0 @@
|
|||
# Release Notes: Mindnet v3.0.0 (WP25)
|
||||
|
||||
**Release Date:** 2026-01-01
|
||||
**Type:** Feature Release - Agentic Multi-Stream RAG Orchestration
|
||||
**Branch:** WP25
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Übersicht
|
||||
|
||||
Diese Version markiert den Übergang von Mindnet von einer klassischen, linearen RAG-Architektur zu einer **Agentic Multi-Stream Engine**. Das System agiert nun als intelligenter Orchestrator, der Nutzeranfragen analysiert, in parallele Wissens-Streams aufteilt und diese zu einer kontextreichen, wertebasierten Antwort synthetisiert.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Neue Features
|
||||
|
||||
### Agentic Multi-Stream RAG Orchestration
|
||||
|
||||
Mindnet führt nun **parallele Abfragen** in spezialisierten Wissens-Streams aus, anstatt einer einzelnen Suche:
|
||||
|
||||
**Stream-Library:**
|
||||
* **Values Stream:** Extrahiert Identität, Ethik und Prinzipien (`value`, `principle`, `belief`, `trait`, `boundary`, `need`, `motivation`).
|
||||
* **Facts Stream:** Liefert operative Daten zu Projekten, Tasks und Status (`project`, `decision`, `task`, `goal`, `event`, `state`).
|
||||
* **Biography Stream:** Greift auf persönliche Erfahrungen und Journal-Einträge zu (`experience`, `journal`, `profile`, `person`).
|
||||
* **Risk Stream:** Identifiziert Hindernisse und potenzielle Gefahren (`risk`, `obstacle`, `bias`).
|
||||
* **Tech Stream:** Bündelt technisches Wissen, Code und Dokumentation (`concept`, `source`, `glossary`, `idea`, `insight`, `skill`, `habit`).
|
||||
|
||||
**Vorteile:**
|
||||
* **Präzise Kontext-Ladung:** Jeder Stream fokussiert auf spezifische Wissensbereiche.
|
||||
* **Parallele Ausführung:** Alle Streams werden gleichzeitig abgefragt (asyncio.gather).
|
||||
* **Stream-Tracing:** Jeder Treffer wird mit `stream_origin` markiert für Feedback-Optimierung.
|
||||
* **Fehler-Resilienz:** Einzelne Stream-Fehler blockieren nicht die gesamte Anfrage.
|
||||
|
||||
### Intent-basiertes Routing ("The Brain")
|
||||
|
||||
Der Router wurde zu einem **hybriden System** erweitert, das Anfragen klassifiziert, bevor die Suche beginnt:
|
||||
|
||||
**Strategien:**
|
||||
* **FACT_WHAT:** Wissen/Listen (z.B. "Was ist...", "Welche sind...").
|
||||
* **FACT_WHEN:** Zeitpunkte (z.B. "Wann...", "Datum...").
|
||||
* **DECISION:** Beratung (z.B. "Soll ich...", "Sollte ich...", "Empfehlung...").
|
||||
* **EMPATHY:** Reflexion (z.B. "Fühle...", "Stress...", "Angst...").
|
||||
* **CODING:** Technik (z.B. "Code...", "Python...", "Bug...").
|
||||
* **INTERVIEW:** Datenerfassung (z.B. "Neues Projekt...", "Erfahrung...").
|
||||
|
||||
**Hybrid-Modus:**
|
||||
* **Keyword Fast-Path:** Sofortige Erkennung von Triggern wie "Soll ich" oder "Wann" ohne LLM-Call.
|
||||
* **LLM Slow-Path:** Komplexe semantische Analyse für unklare Anfragen.
|
||||
* **Type Keywords:** Automatische Erkennung von Objekt-Typen für Interview-Modus.
|
||||
|
||||
**Vorteil:** Reduziert Latenz durch schnelle Keyword-Erkennung, nutzt LLM nur bei Bedarf.
|
||||
|
||||
### Wissens-Synthese
|
||||
|
||||
Die Zusammenführung der Daten erfolgt über spezialisierte Templates in der `prompts.yaml`:
|
||||
|
||||
**Template-Struktur:**
|
||||
* Explizite Variablen für jeden Stream (z.B. `{values_stream}`, `{risk_stream}`).
|
||||
* **Pre-Initialization:** Alle möglichen Stream-Variablen werden vorab initialisiert (verhindert KeyErrors).
|
||||
* **Provider-spezifische Templates:** Separate Versionen für Ollama, Gemini und OpenRouter.
|
||||
|
||||
**Synthese-Strategien:**
|
||||
* **FACT_WHAT/FACT_WHEN:** Kombiniert Fakten, Biographie und Technik.
|
||||
* **DECISION:** Wägt Fakten gegen Werte ab, evaluiert Risiken.
|
||||
* **EMPATHY:** Fokus auf Biographie und Werte.
|
||||
* **CODING:** Technik und Fakten.
|
||||
|
||||
**Vorteil:** Ermöglicht dem LLM eine differenzierte Abwägung zwischen Fakten und persönlichen Werten.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Verbesserungen
|
||||
|
||||
### Stream-Konfiguration & Typ-Synchronisation
|
||||
|
||||
Jeder Stream nutzt individuelle **Edge-Boosts** und **Filter-Types**, die strikt mit der `types.yaml` (v2.7.0) synchronisiert sind:
|
||||
|
||||
**Beispiele:**
|
||||
* **Values Stream:** `guides: 3.0`, `enforced_by: 2.5`, `based_on: 2.0`.
|
||||
* **Facts Stream:** `part_of: 2.0`, `depends_on: 1.5`, `implemented_in: 1.5`.
|
||||
* **Risk Stream:** `blocks: 2.5`, `impacts: 2.0`, `risk_of: 2.5`.
|
||||
|
||||
**Vorteil:** Präzise Gewichtung der Graph-Kanten je nach Wissensbereich.
|
||||
|
||||
### Template-Robustheit
|
||||
|
||||
**Pre-Initialization:** Automatische Vor-Initialisierung aller Stream-Variablen in der `DecisionEngine` verhindert `KeyError`-Abstürze bei unvollständigen Konfigurationen.
|
||||
|
||||
**Fallback-Mechanismus:** Bei Template-Fehlern wird ein vereinfachter Prompt mit allen verfügbaren Stream-Ergebnissen verwendet.
|
||||
|
||||
### Ollama Context-Throttling
|
||||
|
||||
Vor der Übergabe an Ollama prüft der Chat-Router, ob der Kontext (RAG-Hits) die Grenze von `MAX_OLLAMA_CHARS` überschreitet (Standard: 10.000) und kürzt diesen ggf.
|
||||
|
||||
**Vorteil:** Verhindert VRAM-Überlastung bei großen Kontexten.
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Bugfixes
|
||||
|
||||
- ✅ **Behoben:** Vault-Import Performance - "Empty Response Guard" korrigiert, damit kurze Ingest-Antworten (YES/NO) nicht mehr fälschlicherweise langsame Fallbacks auslösen.
|
||||
- ✅ **Behoben:** Cloud-Resilienz - Sicherheitscheck für das `choices`-Array in `_execute_openrouter` implementiert, um JSON-Errors bei überlasteten APIs zu verhindern.
|
||||
- ✅ **Behoben:** Template-Robustheit - Automatische Vor-Initialisierung aller Stream-Variablen verhindert `KeyError`-Abstürze.
|
||||
- ✅ **Behoben:** Intent-Kollision - Generische Begriffe (wie "Projekt") wurden aus den Keyword-Triggern entfernt, um sicherzustellen, dass strategische Fragen (z.B. "Soll ich...?") korrekt als `DECISION` geroutet werden.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Breaking Changes & Migration
|
||||
|
||||
### Keine Migration erforderlich
|
||||
|
||||
**WICHTIG:** Diese Version ist **rückwärtskompatibel**. Bestehende Vaults funktionieren ohne Re-Import.
|
||||
|
||||
**Konfigurations-Updates:**
|
||||
* `decision_engine.yaml` wurde auf v3.1.6 aktualisiert (Multi-Stream Struktur).
|
||||
* `prompts.yaml` wurde auf v3.1.2 aktualisiert (Stream-Templates).
|
||||
* **Empfehlung:** Backup der alten Konfigurationsdateien vor dem Update.
|
||||
|
||||
---
|
||||
|
||||
## 📚 API-Änderungen
|
||||
|
||||
### Erweiterte DTOs
|
||||
|
||||
**QueryHit (erweitert):**
|
||||
```python
|
||||
class QueryHit(BaseModel):
|
||||
# ... bestehende Felder ...
|
||||
stream_origin: Optional[str] = None # Neu: Name des Ursprungs-Streams
|
||||
```
|
||||
|
||||
**ChatResponse (erweitert):**
|
||||
```python
|
||||
class ChatResponse(BaseModel):
|
||||
# ... bestehende Felder ...
|
||||
intent: Optional[str] = "FACT" # Neu: Die gewählte WP-25 Strategie
|
||||
intent_source: Optional[str] = "LLM_Router" # Neu: Quelle der Intent-Erkennung
|
||||
```
|
||||
|
||||
**Impact für API-Consumer:**
|
||||
* Frontend kann `stream_origin` für visuelle Kennzeichnung der Quellen nutzen.
|
||||
* `intent` und `intent_source` ermöglichen Debugging und Analytics.
|
||||
|
||||
---
|
||||
|
||||
## 📖 Dokumentation
|
||||
|
||||
Alle relevanten Dokumente wurden aktualisiert:
|
||||
|
||||
- ✅ `03_tech_chat_backend.md`: Agentic Multi-Stream RAG, Intent-basiertes Routing, Wissens-Synthese
|
||||
- ✅ `03_tech_configuration.md`: decision_engine.yaml, prompts.yaml (Stream-Struktur)
|
||||
- ✅ `03_tech_api_reference.md`: Erweiterte DTOs (stream_origin, intent)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Technische Details
|
||||
|
||||
### Geänderte Module
|
||||
|
||||
**Decision Engine:**
|
||||
- `app/core/retrieval/decision_engine.py`: Multi-Stream Orchestrator (v1.0.3)
|
||||
- Paralleles Retrieval via `_execute_parallel_streams()`
|
||||
- Stream-Tracing mit `stream_origin`
|
||||
- Pre-Initialization der Stream-Variablen
|
||||
|
||||
**Chat Router:**
|
||||
- `app/routers/chat.py`: Hybrid Router Integration (v3.0.2)
|
||||
- Keyword Fast-Path + LLM Slow-Path
|
||||
- Multi-Stream Orchestrierung
|
||||
- Ollama Context-Throttling
|
||||
|
||||
**LLM Service:**
|
||||
- `app/services/llm_service.py`: Ingest-Stability Patch (v3.4.2)
|
||||
- Entfernung des <5-Zeichen Guards
|
||||
- Empty Response Guard für OpenRouter
|
||||
- Lazy Initialization der DecisionEngine
|
||||
|
||||
**DTOs:**
|
||||
- `app/models/dto.py`: Stream-Tracing Support (v0.7.1)
|
||||
- `stream_origin` in `QueryHit`
|
||||
- `intent` und `intent_source` in `ChatResponse`
|
||||
|
||||
**Main:**
|
||||
- `app/main.py`: Lifespan Management (v1.0.0)
|
||||
- Startup-Integritäts-Check
|
||||
- Ressourcen-Cleanup beim Shutdown
|
||||
- Globale Fehlerbehandlung
|
||||
|
||||
**Konfiguration:**
|
||||
- `config/decision_engine.yaml`: Multi-Stream Konfiguration (v3.1.6)
|
||||
- `config/prompts.yaml`: Stream-Templates (v3.1.2)
|
||||
|
||||
### Versionsnummern
|
||||
|
||||
- Decision Engine: **v1.0.3**
|
||||
- Chat Router: **v3.0.2**
|
||||
- LLM Service: **v3.4.2**
|
||||
- DTOs: **v0.7.1**
|
||||
- Main: **v1.0.0**
|
||||
- decision_engine.yaml: **v3.1.6**
|
||||
- prompts.yaml: **v3.1.2**
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Upgrade-Pfad
|
||||
|
||||
### Für Administratoren
|
||||
|
||||
1. **Code aktualisieren:**
|
||||
```bash
|
||||
git pull origin main
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **Konfiguration prüfen:**
|
||||
```bash
|
||||
# Backup der alten Konfigurationen
|
||||
cp config/decision_engine.yaml config/decision_engine.yaml.backup
|
||||
cp config/prompts.yaml config/prompts.yaml.backup
|
||||
|
||||
# Prüfen, ob neue Konfigurationen vorhanden sind
|
||||
ls -la config/decision_engine.yaml config/prompts.yaml
|
||||
```
|
||||
|
||||
3. **Services neu starten:**
|
||||
```bash
|
||||
sudo systemctl restart mindnet-prod
|
||||
sudo systemctl restart mindnet-ui-prod
|
||||
```
|
||||
|
||||
### Für Entwickler
|
||||
|
||||
- Keine Code-Änderungen erforderlich, wenn nur API genutzt wird
|
||||
- Frontend kann neue Felder (`stream_origin`, `intent`, `intent_source`) optional nutzen
|
||||
- Stream-Tracing ermöglicht Feedback-Optimierung pro Wissensbereich
|
||||
|
||||
---
|
||||
|
||||
## 📝 Bekannte Einschränkungen
|
||||
|
||||
- **Stream-Parallelität:** Maximale Anzahl paralleler Streams ist durch asyncio-Limits begrenzt (typischerweise 5-10 Streams).
|
||||
- **Kontext-Größe:** Große Kontexte (>10.000 Zeichen) werden bei Ollama automatisch gekürzt.
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Ausblick (WP-25a: Agentic Refinement)
|
||||
|
||||
Auf Basis des stabilen WP-25 Fundaments sind folgende Erweiterungen geplant:
|
||||
|
||||
* **Pre-Synthesis:** LLM-basierte Komprimierung überlanger Streams vor der finalen Antwortgenerierung.
|
||||
* **Kontext-Budgeting:** Intelligente Verteilung der verfügbaren Token auf die aktivierten Streams.
|
||||
* **Stream-specific Provider:** Zuweisung unterschiedlicher KI-Modelle pro Wissensbereich (z.B. lokales Ollama für Identitäts-Werte, Gemini für technische Fakten).
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Danksagungen
|
||||
|
||||
Diese Version wurde durch umfangreiche Architektur-Überarbeitung ermöglicht. Besonderer Fokus lag auf:
|
||||
- Transformation zu einer Agentic Multi-Stream Engine
|
||||
- Intelligente Intent-Erkennung mit Hybrid-Routing
|
||||
- Parallele Wissens-Synthese mit wertebasierter Abwägung
|
||||
- Robustheit und Fehler-Resilienz
|
||||
|
||||
---
|
||||
|
||||
**Vollständige Changelog:** Siehe Git-Commits für detaillierte Änderungen
|
||||
**Support:** Bei Fragen siehe [Admin Operations Guide](../04_Operations/04_admin_operations.md)
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
# Branch Merge Commit: WP-25a
|
||||
|
||||
**Branch:** `WP25a`
|
||||
**Target:** `main`
|
||||
**Version:** v3.1.0
|
||||
**Date:** 2026-01-02
|
||||
|
||||
---
|
||||
|
||||
## Commit Message
|
||||
|
||||
```
|
||||
feat: Mixture of Experts (MoE) & Fallback-Kaskade (v3.0.0)
|
||||
|
||||
### Mixture of Experts (MoE) Architektur
|
||||
- Übergang von provider-basierter zu profilbasierter Experten-Steuerung
|
||||
- Zentrale Experten-Registry (`llm_profiles.yaml` v1.3.0)
|
||||
- Aufgabenspezifische Profile: synthesis_pro, tech_expert, compression_fast, ingest_validator, identity_safe, embedding_expert
|
||||
- Hardware-Optimierung: Lokaler Anker (Ollama/Phi-3) für maximale Privacy
|
||||
|
||||
### Rekursive Fallback-Kaskade
|
||||
- Implementierung in `app/services/llm_service.py` (v3.5.2)
|
||||
- Automatische Fallback-Logik bei Provider-Fehlern
|
||||
- Schutz gegen Zirkel-Referenzen via `visited_profiles`-Tracking
|
||||
- Background-Semaphore für parallele Tasks
|
||||
|
||||
### Pre-Synthesis Kompression (Module A)
|
||||
- Asynchrone Verdichtung überlanger Wissens-Streams
|
||||
- Konfigurierbare Schwellenwerte pro Stream (`compression_threshold`)
|
||||
- Profil-gesteuerte Kompression via `compression_profile`
|
||||
- Parallelisierung über `asyncio.gather()`
|
||||
|
||||
### Profilgesteuerte Ingestion
|
||||
- Semantische Kanten-Validierung via `ingest_validator` (Temperature 0.0)
|
||||
- Embedding-Konsolidierung über `embedding_expert` Profil
|
||||
- Entfernung der Embedding-Konfiguration aus `.env`
|
||||
|
||||
### Startup-Schutz & Audit-Fixes
|
||||
- Validierung von `llm_profiles.yaml` und `decision_engine.yaml` beim Booten
|
||||
- Behebung der Sicherheitslücke in `DecisionEngine` (Fallback-Aufrufe nutzen nun `profile_name`)
|
||||
- Circular Import Fix: Ingestion-Module nutzen neutrale `app.core.registry`
|
||||
|
||||
### Code-Komponenten
|
||||
- `app/services/llm_service.py`: v3.5.2 (Rekursive Fallback-Kaskade)
|
||||
- `app/core/retrieval/decision_engine.py`: v1.2.1 (Profile-Driven Orchestration)
|
||||
- `app/core/ingestion/ingestion_processor.py`: v2.14.0 (Profilgesteuerte Validierung)
|
||||
- `app/core/ingestion/ingestion_validation.py`: v2.13.0 (MoE-Profil Integration)
|
||||
- `app/services/embeddings_client.py`: v2.6.0 (Profil-basierte Modell-Auflösung)
|
||||
- `app/main.py`: v1.1.0 (Startup-Validierung)
|
||||
|
||||
### Konfiguration
|
||||
- `config/llm_profiles.yaml`: v1.3.0 (Zentrale Experten-Registry)
|
||||
- `config/decision_engine.yaml`: v3.2.2 (Decoupled MoE Logic, compression_thresholds)
|
||||
|
||||
### Dokumentation
|
||||
- `03_tech_configuration.md`: llm_profiles.yaml Dokumentation
|
||||
- `03_tech_chat_backend.md`: MoE Architektur und Fallback-Kaskade
|
||||
- `02_concept_ai_personality.md`: Mixture of Experts Konzept
|
||||
- `03_tech_ingestion_pipeline.md`: Profilgesteuerte Validierung
|
||||
- `00_glossary.md`: Neue Begriffe (MoE, Profile, Fallback-Kaskade)
|
||||
- `05_developer_guide.md`: Teach-the-AI mit Profilen
|
||||
- `04_admin_operations.md`: Konfigurations-Updates
|
||||
- `06_active_roadmap.md`: WP25a als abgeschlossen markiert
|
||||
|
||||
### Breaking Changes
|
||||
- Keine Breaking Changes für Endbenutzer
|
||||
- ENV-Variablen `MINDNET_LLM_PROVIDER`, `MINDNET_LLM_MODEL` etc. dienen nur noch als Fallback
|
||||
- Neue Konfigurationsdatei `llm_profiles.yaml` ist erforderlich (Startup-Validierung)
|
||||
|
||||
### Migration
|
||||
- Administratoren müssen `config/llm_profiles.yaml` erstellen
|
||||
- System startet nicht, wenn `llm_profiles.yaml` fehlt oder ungültig ist
|
||||
- ENV-Variablen bleiben als Fallback erhalten
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ WP-25a ist zu 100% implementiert und audit-geprüft.
|
||||
**Nächster Schritt:** WP-25b (Prompt-Orchestration & Model-Specific Tuning).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
Dieser Merge führt die **Mixture of Experts (MoE) Architektur** in MindNet ein. Das System nutzt nun eine profilbasierte Experten-Steuerung statt einer globalen Provider-Konfiguration. Jede Systemaufgabe wird einem dedizierten Profil zugewiesen, das Modell, Provider und Parameter unabhängig definiert.
|
||||
|
||||
**Kern-Features:**
|
||||
- Zentrale Experten-Registry (`llm_profiles.yaml`)
|
||||
- Rekursive Fallback-Kaskade mit Schutz gegen Zirkel-Referenzen
|
||||
- Pre-Synthesis Kompression für überlange Wissens-Streams
|
||||
- Profilgesteuerte Ingestion mit deterministischer Validierung
|
||||
- Startup-Schutz und Audit-Fixes
|
||||
|
||||
**Technische Integrität:**
|
||||
- Alle LLM-Aufrufe nutzen nun die Profilsteuerung
|
||||
- Startup-Validierung verhindert fehlerhafte Konfigurationen
|
||||
- Circular Import Fix verbessert Wartbarkeit
|
||||
|
||||
**Dokumentation:**
|
||||
- Vollständige Aktualisierung aller relevanten Dokumente
|
||||
- Neue Begriffe im Glossar
|
||||
- Konfigurations-Referenz erweitert
|
||||
- Developer Guide aktualisiert
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
# MindNet v3.0.0 - Release Notes: WP-25a
|
||||
|
||||
**Release Date:** 2026-01-02
|
||||
**Type:** Feature Release - Mixture of Experts (MoE) & Fallback-Kaskade
|
||||
**Version:** 3.1.0 (WP-25a)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Überblick
|
||||
|
||||
Mit WP-25a wurde MindNet von einer provider-basierten Steuerung auf eine **profilbasierte Experten-Architektur (Mixture of Experts)** umgestellt. Jede Systemaufgabe (Synthese, Ingestion-Validierung, Routing, Kompression) wird nun einem dedizierten Profil zugewiesen, das Modell, Provider und Parameter unabhängig von der globalen Konfiguration definiert.
|
||||
|
||||
Diese Version markiert einen fundamentalen Architektur-Sprung: Von einer monolithischen LLM-Konfiguration hin zu einer modularen, aufgabenspezifischen Experten-Steuerung mit automatischer Resilienz.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Neue Features
|
||||
|
||||
### 1. Mixture of Experts (MoE) Architektur
|
||||
|
||||
**Zentrale Experten-Steuerung (`llm_profiles.yaml` v1.3.0):**
|
||||
* Einführung von Experten-Profilen für spezifische Aufgaben:
|
||||
* `synthesis_pro`: Hochwertige Synthese für Chat-Antworten
|
||||
* `tech_expert`: Fachspezialist für Code & Technik (Claude 3.5 Sonnet)
|
||||
* `compression_fast`: Schnelle Kompression & Routing (Mistral 7B)
|
||||
* `ingest_validator`: Deterministische Validierung (Temperature 0.0)
|
||||
* `identity_safe`: Lokaler Anker (Ollama/Phi-3) für maximale Privacy
|
||||
* `embedding_expert`: Zentrale Steuerung des Embedding-Modells
|
||||
|
||||
**Vorteile:**
|
||||
* **Aufgabenspezifische Optimierung:** Jede Aufgabe nutzt das optimale Modell
|
||||
* **Hardware-Optimierung:** Lokaler Anker für kleine Hardware-Umgebungen
|
||||
* **Wartbarkeit:** Zentrale Konfiguration statt verstreuter ENV-Variablen
|
||||
|
||||
### 2. Rekursive Fallback-Kaskade
|
||||
|
||||
**Implementierung (`app/services/llm_service.py` v3.5.2):**
|
||||
* Automatische Fallback-Logik bei Provider-Fehlern:
|
||||
1. Versucht primäres Profil (z.B. `synthesis_pro`)
|
||||
2. Bei Fehler → `fallback_profile` (z.B. `synthesis_backup`)
|
||||
3. Bei weiterem Fehler → nächster Fallback (z.B. `identity_safe`)
|
||||
4. Terminaler Endpunkt: `identity_safe` hat keinen Fallback (lokales Modell)
|
||||
|
||||
**Schutzmechanismen:**
|
||||
* **Zirkuläre Referenzen:** `visited_profiles`-Tracking verhindert Endlosschleifen
|
||||
* **Background-Semaphore:** Parallele Tasks werden gedrosselt (konfigurierbar via `BACKGROUND_LIMIT`)
|
||||
|
||||
### 3. Pre-Synthesis Kompression (Module A)
|
||||
|
||||
**Implementierung (`app/core/retrieval/decision_engine.py` v1.2.1):**
|
||||
* Wissens-Streams, die den Schwellenwert (`compression_threshold`) überschreiten, werden **asynchron verdichtet**, bevor sie die Synthese erreichen
|
||||
* **Konfigurierbar pro Stream:** Z.B. 2500 Zeichen für Values Stream, 3500 für Facts Stream
|
||||
* **Profil-gesteuert:** Nutzt `compression_profile` (z.B. `compression_fast` für schnelle Zusammenfassung)
|
||||
* **Parallelisierung:** Mehrere Streams können gleichzeitig komprimiert werden
|
||||
|
||||
**Vorteile:**
|
||||
* Reduziert Token-Verbrauch bei langen Streams
|
||||
* Beschleunigt Synthese durch kürzere Kontexte
|
||||
* Erhält Relevanz durch intelligente Zusammenfassung
|
||||
|
||||
### 4. Profilgesteuerte Ingestion (Wissens-Gatekeeper)
|
||||
|
||||
**Implementierung:**
|
||||
* `app/core/ingestion/ingestion_processor.py` v2.14.0
|
||||
* `app/core/ingestion/ingestion_validation.py` v2.13.0
|
||||
|
||||
**Features:**
|
||||
* Semantische Kanten-Validierung erfolgt zwingend über das MoE-Profil `ingest_validator` (Temperature 0.0 für Determinismus)
|
||||
* **Embedding-Konsolidierung:** Der `EmbeddingsClient` (v2.6.0) bezieht Modellvorgaben und Dimensionen direkt aus dem Profil `embedding_expert`
|
||||
* Entfernung der Embedding-Konfiguration aus der `.env` zugunsten zentraler Profil-Registry
|
||||
|
||||
### 5. Startup-Schutz & Audit-Fixes
|
||||
|
||||
**Implementierung (`app/main.py` v1.1.0):**
|
||||
* Verifiziert beim Booten die Existenz und Validität von `llm_profiles.yaml` und `decision_engine.yaml`
|
||||
* **Audit-Fix:** Behebung einer Sicherheitslücke in der `DecisionEngine`, durch die Fallback-Aufrufe bei Template-Fehlern die Profilsteuerung umgangen hätten
|
||||
* **Circular Import Fix:** Ingestion-Module nutzen nun die neutrale `app.core.registry` für Text-Bereinigung und Registry-Lookups
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Technische Änderungen
|
||||
|
||||
### Konfigurationsdateien
|
||||
|
||||
**Neue Datei:**
|
||||
* `config/llm_profiles.yaml` v1.3.0: Zentrale Experten-Registry
|
||||
|
||||
**Aktualisierte Dateien:**
|
||||
* `config/decision_engine.yaml` v3.2.2: Decoupled MoE Logic, Integration von `compression_thresholds`
|
||||
|
||||
### Code-Komponenten
|
||||
|
||||
| Komponente | Version | Änderungen |
|
||||
| :--- | :--- | :--- |
|
||||
| `app/services/llm_service.py` | v3.5.2 | Rekursive Fallback-Kaskade, Profil-Auflösung |
|
||||
| `app/core/retrieval/decision_engine.py` | v1.2.1 | Profile-Driven Orchestration, Pre-Synthesis Kompression |
|
||||
| `app/core/ingestion/ingestion_processor.py` | v2.14.0 | Profilgesteuerte Validierung |
|
||||
| `app/core/ingestion/ingestion_validation.py` | v2.13.0 | MoE-Profil `ingest_validator` |
|
||||
| `app/services/embeddings_client.py` | v2.6.0 | Profil-basierte Modell-Auflösung |
|
||||
| `app/main.py` | v1.1.0 | Startup-Validierung der YAML-Dateien |
|
||||
|
||||
### Environment-Variablen
|
||||
|
||||
**Neue Variable:**
|
||||
* `MINDNET_LLM_PROFILES_PATH`: Pfad zur Profil-Registry (Default: `config/llm_profiles.yaml`)
|
||||
|
||||
**Legacy (nur noch Fallback):**
|
||||
* `MINDNET_LLM_PROVIDER`, `MINDNET_LLM_MODEL`, etc. dienen nur noch als Fallback, wenn kein Profil angegeben wird
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Behobene Probleme
|
||||
|
||||
- ✅ **Behoben:** Sicherheitslücke in der `DecisionEngine` - Fallback-Aufrufe nutzen nun korrekt `profile_name`
|
||||
- ✅ **Behoben:** Circular Import zwischen Ingestion-Modulen und Registry
|
||||
- ✅ **Behoben:** Fehlende Startup-Validierung der YAML-Konfigurationen
|
||||
|
||||
---
|
||||
|
||||
## 📚 Dokumentation
|
||||
|
||||
**Aktualisierte Dokumente:**
|
||||
- ✅ `03_tech_configuration.md`: llm_profiles.yaml Dokumentation
|
||||
- ✅ `03_tech_chat_backend.md`: MoE Architektur und Fallback-Kaskade
|
||||
- ✅ `02_concept_ai_personality.md`: Mixture of Experts Konzept
|
||||
- ✅ `03_tech_ingestion_pipeline.md`: Profilgesteuerte Validierung
|
||||
- ✅ `00_glossary.md`: Neue Begriffe (MoE, Profile, Fallback-Kaskade)
|
||||
- ✅ `05_developer_guide.md`: Teach-the-AI mit Profilen
|
||||
- ✅ `04_admin_operations.md`: Konfigurations-Updates
|
||||
- ✅ `06_active_roadmap.md`: WP25a als abgeschlossen markiert
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Migration & Upgrade
|
||||
|
||||
### Für Administratoren
|
||||
|
||||
1. **Neue Konfigurationsdatei erstellen:**
|
||||
```bash
|
||||
cp config/llm_profiles.yaml.example config/llm_profiles.yaml
|
||||
# Anpassen nach Bedarf
|
||||
```
|
||||
|
||||
2. **Startup-Validierung prüfen:**
|
||||
```bash
|
||||
# System startet nicht, wenn llm_profiles.yaml fehlt oder ungültig ist
|
||||
python3 -m app.main
|
||||
```
|
||||
|
||||
3. **ENV-Variablen (optional):**
|
||||
Die `.env` Variablen `MINDNET_LLM_PROVIDER`, `MINDNET_LLM_MODEL` etc. dienen nur noch als Fallback. Die primäre Steuerung erfolgt über `llm_profiles.yaml`.
|
||||
|
||||
### Für Entwickler
|
||||
|
||||
**API-Änderungen:**
|
||||
* `LLMService.generate_raw_response()` unterstützt nun `profile_name` Parameter
|
||||
* Fallback-Kaskade erfolgt automatisch bei Fehlern
|
||||
* `visited_profiles`-Tracking verhindert Zirkel-Referenzen
|
||||
|
||||
**Konfiguration:**
|
||||
* Neue Profile können in `llm_profiles.yaml` definiert werden
|
||||
* `decision_engine.yaml` referenziert Profile über `router_profile`, `llm_profile`, `compression_profile`
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Ausblick (WP-25b: Prompt-Orchestration & Model-Specific Tuning)
|
||||
|
||||
- Pre-Synthesis: LLM-basierte Komprimierung überlanger Streams (✅ bereits implementiert)
|
||||
- Kontext-Budgeting: Intelligente Token-Verteilung
|
||||
- Stream-specific Provider: Unterschiedliche KI-Modelle pro Wissensbereich
|
||||
- Prompt-Orchestration: Dynamische Prompt-Anpassung basierend auf Profil und Kontext
|
||||
|
||||
---
|
||||
|
||||
## 📊 Metriken & Performance
|
||||
|
||||
**Erwartete Verbesserungen:**
|
||||
* **Resilienz:** Automatische Fallback-Kaskade reduziert Ausfallzeiten
|
||||
* **Token-Effizienz:** Pre-Synthesis Kompression reduziert Token-Verbrauch bei langen Streams
|
||||
* **Determinismus:** Profilgesteuerte Validierung (Temperature 0.0) erhöht Konsistenz
|
||||
* **Wartbarkeit:** Zentrale Profil-Registry vereinfacht Konfigurations-Management
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ WP-25a ist zu 100% implementiert und audit-geprüft.
|
||||
**Nächster Schritt:** WP-25b (Prompt-Orchestration & Model-Specific Tuning).
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
# Branch Merge Commit: WP-25b
|
||||
|
||||
**Branch:** `WP25b`
|
||||
**Target:** `main`
|
||||
**Version:** v3.1.1
|
||||
**Date:** 2026-01-02
|
||||
|
||||
---
|
||||
|
||||
## Commit Message
|
||||
|
||||
```
|
||||
feat: Lazy-Prompt-Orchestration & Full Resilience (v3.1.1)
|
||||
|
||||
### Hierarchisches Prompt-Resolution-System
|
||||
- Dreistufige Auflösungs-Logik: Level 1 (Modell-ID) → Level 2 (Provider) → Level 3 (Default)
|
||||
- Modell-spezifische Optimierungen für Gemini 2.0, Llama 3.3, Qwen 2.5
|
||||
- PROMPT-TRACE Logging für vollständige Transparenz
|
||||
- Implementierung in `app/services/llm_service.py` (v3.5.5)
|
||||
|
||||
### Lazy-Prompt-Orchestration
|
||||
- Prompts werden erst zur Laufzeit geladen, basierend auf aktivem Modell
|
||||
- Parameter: `prompt_key` und `variables` statt vorformatierter Strings
|
||||
- Maximale Resilienz bei Modell-Fallbacks (Cloud → Local)
|
||||
- Vollständige Integration in Chat, Ingestion und DecisionEngine
|
||||
|
||||
### Ultra-robustes Intent-Parsing
|
||||
- Regex-basierter Parser bereinigt Modell-Artefakte (z.B. `CODING[/S]` → `CODING`)
|
||||
- Implementierung in `app/core/retrieval/decision_engine.py` (v1.3.2)
|
||||
- Fehlerresistenz gegen Stop-Marker, Newlines oder Modell-Plaudereien
|
||||
|
||||
### Differenzierte Ingestion-Validierung
|
||||
- Unterscheidung zwischen transienten (Netzwerk) und permanenten (Config) Fehlern
|
||||
- Transiente Fehler erlauben Kante (Datenverlust vermeiden)
|
||||
- Permanente Fehler lehnen Kante ab (Graph-Qualität schützen)
|
||||
- Implementierung in `app/core/ingestion/ingestion_validation.py` (v2.14.0)
|
||||
|
||||
### Code-Komponenten
|
||||
- `app/services/llm_service.py`: v3.5.5 (Hierarchische Prompt-Resolution, Lazy-Loading)
|
||||
- `app/core/retrieval/decision_engine.py`: v1.3.2 (Ultra-robustes Intent-Parsing)
|
||||
- `app/core/ingestion/ingestion_validation.py`: v2.14.0 (Lazy-Prompt-Integration)
|
||||
- `app/routers/chat.py`: v3.0.3 (Lazy-Prompt-Loading für Chat-Synthese)
|
||||
|
||||
### Konfiguration
|
||||
- `config/prompts.yaml`: v3.2.2 (Hierarchische Struktur mit Modell-spezifischen Overrides)
|
||||
- 100% Erhalt der Original-Prompts aus v3.1.2 für Provider-Ebene
|
||||
- Integration von Modell-spezifischen Overrides
|
||||
- Hinzufügen von `compression_template`
|
||||
|
||||
### Dokumentation
|
||||
- `03_tech_chat_backend.md`: Hierarchisches Prompt-Resolution-System
|
||||
- `03_tech_configuration.md`: prompts.yaml hierarchische Struktur
|
||||
- `02_concept_ai_personality.md`: Lazy-Prompt-Orchestration Konzept
|
||||
- `03_tech_ingestion_pipeline.md`: Differenzierte Validierung
|
||||
- `00_glossary.md`: Neue Begriffe (Lazy-Prompt, PROMPT-TRACE)
|
||||
- `05_developer_guide.md`: Lazy-Prompt-Orchestration für Entwickler
|
||||
- `06_active_roadmap.md`: WP25b als abgeschlossen markiert
|
||||
|
||||
### Breaking Changes
|
||||
- Keine Breaking Changes für Endbenutzer
|
||||
- Vorformatierte Prompts werden weiterhin unterstützt (Abwärtskompatibilität)
|
||||
- Neue API-Parameter `prompt_key` und `variables` optional
|
||||
|
||||
### Migration
|
||||
- Keine Migration erforderlich
|
||||
- System funktioniert ohne Änderungen
|
||||
- Optional: Modell-spezifische Prompts können in `prompts.yaml` definiert werden
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ WP-25b ist zu 100% implementiert und audit-geprüft.
|
||||
**Nächster Schritt:** WP-25c (Kontext-Budgeting & Erweiterte Prompt-Optimierung).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
Dieser Merge führt die **Lazy-Prompt-Orchestration** in MindNet ein. Das System nutzt nun eine hierarchische Prompt-Auflösung mit Lazy-Loading, die Prompts erst zur Laufzeit lädt, basierend auf dem exakt aktiven Modell.
|
||||
|
||||
**Kern-Features:**
|
||||
- Hierarchisches Prompt-Resolution-System (3-stufig)
|
||||
- Lazy-Prompt-Orchestration mit modell-spezifischem Tuning
|
||||
- Ultra-robustes Intent-Parsing via Regex
|
||||
- Differenzierte Ingestion-Validierung
|
||||
- PROMPT-TRACE Logging für vollständige Transparenz
|
||||
|
||||
**Technische Integrität:**
|
||||
- Alle LLM-Aufrufe nutzen nun Lazy-Prompt-Loading
|
||||
- Modell-Artefakte werden zuverlässig bereinigt
|
||||
- Fehlerbehandlung differenziert zwischen transienten und permanenten Fehlern
|
||||
|
||||
**Dokumentation:**
|
||||
- Vollständige Aktualisierung aller relevanten Dokumente
|
||||
- Neue Begriffe im Glossar
|
||||
- Konfigurations-Referenz erweitert
|
||||
- Developer Guide aktualisiert
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user