erster Aufschlag WP22
This commit is contained in:
parent
77a6db7e92
commit
e2ee5df815
|
|
@ -3,9 +3,10 @@ FILE: app/core/ingestion.py
|
|||
DESCRIPTION: Haupt-Ingestion-Logik.
|
||||
FIX: Korrekte Priorisierung von Frontmatter für chunk_profile und retriever_weight.
|
||||
Lade Chunk-Config basierend auf dem effektiven Profil, nicht nur dem Notiz-Typ.
|
||||
VERSION: 2.7.0 (Fix: Frontmatter Overrides & Config Loading)
|
||||
WP-22: Integration von Content Lifecycle (Status) und Edge Registry.
|
||||
VERSION: 2.8.0 (WP-22 Lifecycle & Registry)
|
||||
STATUS: Active
|
||||
DEPENDENCIES: app.core.parser, app.core.note_payload, app.core.chunker, app.core.derive_edges, app.core.qdrant*, app.services.embeddings_client
|
||||
DEPENDENCIES: app.core.parser, app.core.note_payload, app.core.chunker, app.core.derive_edges, app.core.qdrant*, app.services.embeddings_client, app.services.edge_registry
|
||||
EXTERNAL_CONFIG: config/types.yaml
|
||||
"""
|
||||
import os
|
||||
|
|
@ -39,6 +40,7 @@ from app.core.qdrant_points import (
|
|||
)
|
||||
|
||||
from app.services.embeddings_client import EmbeddingsClient
|
||||
from app.services.edge_registry import registry as edge_registry
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -157,13 +159,20 @@ class IngestionService:
|
|||
logger.error(f"Validation failed for {file_path}: {e}")
|
||||
return {**result, "error": f"Validation failed: {str(e)}"}
|
||||
|
||||
# --- WP-22: Content Lifecycle Gate ---
|
||||
status = fm.get("status", "draft").lower().strip()
|
||||
|
||||
# Hard Skip für System-Dateien
|
||||
if status in ["system", "template", "archive", "hidden"]:
|
||||
logger.info(f"Skipping file {file_path} (Status: {status})")
|
||||
return {**result, "status": "skipped", "reason": f"lifecycle_status_{status}"}
|
||||
|
||||
# 2. Type & Config Resolution (FIXED)
|
||||
# Wir ermitteln erst den Typ
|
||||
note_type = resolve_note_type(fm.get("type"), self.registry)
|
||||
fm["type"] = note_type
|
||||
|
||||
# Dann ermitteln wir die effektiven Werte unter Berücksichtigung des Frontmatters!
|
||||
# Hier lag der Fehler: Vorher wurde einfach überschrieben.
|
||||
effective_profile = effective_chunk_profile_name(fm, note_type, self.registry)
|
||||
effective_weight = effective_retriever_weight(fm, note_type, self.registry)
|
||||
|
||||
|
|
@ -186,6 +195,8 @@ class IngestionService:
|
|||
# Update Payload with explicit effective values (Sicherheit)
|
||||
note_pl["retriever_weight"] = effective_weight
|
||||
note_pl["chunk_profile"] = effective_profile
|
||||
# WP-22: Status speichern für Dynamic Scoring
|
||||
note_pl["status"] = status
|
||||
|
||||
note_id = note_pl["note_id"]
|
||||
except Exception as e:
|
||||
|
|
@ -222,7 +233,6 @@ class IngestionService:
|
|||
body_text = getattr(parsed, "body", "") or ""
|
||||
|
||||
# FIX: Wir laden jetzt die Config für das SPEZIFISCHE Profil
|
||||
# (z.B. wenn User "sliding_short" wollte, laden wir dessen Params)
|
||||
chunk_config = self._get_chunk_config_by_profile(effective_profile, note_type)
|
||||
|
||||
chunks = await assemble_chunks(fm["id"], body_text, fm["type"], config=chunk_config)
|
||||
|
|
@ -244,15 +254,26 @@ class IngestionService:
|
|||
logger.error(f"Embedding failed: {e}")
|
||||
raise RuntimeError(f"Embedding failed: {e}")
|
||||
|
||||
# Raw Edges generieren
|
||||
try:
|
||||
edges = build_edges_for_note(
|
||||
raw_edges = build_edges_for_note(
|
||||
note_id,
|
||||
chunk_pls,
|
||||
note_level_references=note_pl.get("references", []),
|
||||
include_note_scope_refs=note_scope_refs
|
||||
)
|
||||
except TypeError:
|
||||
edges = build_edges_for_note(note_id, chunk_pls)
|
||||
raw_edges = build_edges_for_note(note_id, chunk_pls)
|
||||
|
||||
# --- WP-22: Edge Registry Validation ---
|
||||
edges = []
|
||||
if raw_edges:
|
||||
for edge in raw_edges:
|
||||
original_kind = edge.get("kind", "related_to")
|
||||
# Resolve via Registry (Canonical mapping + Unknown Logging)
|
||||
canonical_kind = edge_registry.resolve(original_kind)
|
||||
edge["kind"] = canonical_kind
|
||||
edges.append(edge)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Processing failed: {e}", exc_info=True)
|
||||
|
|
@ -286,7 +307,6 @@ class IngestionService:
|
|||
logger.error(f"Upsert failed: {e}", exc_info=True)
|
||||
return {**result, "error": f"DB Upsert failed: {e}"}
|
||||
|
||||
# ... (Restliche Methoden wie _fetch_note_payload bleiben unverändert) ...
|
||||
def _fetch_note_payload(self, note_id: str) -> Optional[dict]:
|
||||
from qdrant_client.http import models as rest
|
||||
col = f"{self.prefix}_notes"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
"""
|
||||
FILE: app/core/retriever.py
|
||||
DESCRIPTION: Implementiert die Hybrid-Suche (Vektor + Graph-Expansion) und das Scoring-Modell (Explainability).
|
||||
VERSION: 0.5.3
|
||||
WP-22 Update: Dynamic Edge Boosting & Lifecycle Scoring.
|
||||
VERSION: 0.6.0 (WP-22 Dynamic Scoring)
|
||||
STATUS: Active
|
||||
DEPENDENCIES: app.config, app.models.dto, app.core.qdrant*, app.services.embeddings_client, app.core.graph_adapter
|
||||
LAST_ANALYSIS: 2025-12-15
|
||||
LAST_ANALYSIS: 2025-12-18
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -97,14 +98,29 @@ def _semantic_hits(
|
|||
results.append((str(pid), float(score), dict(payload or {})))
|
||||
return results
|
||||
|
||||
# --- WP-22 Helper: Lifecycle Multipliers ---
|
||||
def _get_status_multiplier(payload: Dict[str, Any]) -> float:
|
||||
"""
|
||||
WP-22: Drafts werden bestraft, Stable Notes belohnt.
|
||||
"""
|
||||
status = str(payload.get("status", "draft")).lower()
|
||||
if status == "stable": return 1.2
|
||||
if status == "active": return 1.0
|
||||
if status == "draft": return 0.8 # Malus für Entwürfe
|
||||
# Fallback für andere oder leere Status
|
||||
return 1.0
|
||||
|
||||
def _compute_total_score(
|
||||
semantic_score: float,
|
||||
payload: Dict[str, Any],
|
||||
edge_bonus: float = 0.0,
|
||||
cent_bonus: float = 0.0,
|
||||
dynamic_edge_boosts: Dict[str, float] = None
|
||||
) -> Tuple[float, float, float]:
|
||||
"""Berechnet total_score."""
|
||||
"""
|
||||
Berechnet total_score.
|
||||
WP-22 Update: Integration von Status-Bonus und Dynamic Edge Boosts.
|
||||
"""
|
||||
raw_weight = payload.get("retriever_weight", 1.0)
|
||||
try:
|
||||
weight = float(raw_weight)
|
||||
|
|
@ -114,7 +130,17 @@ def _compute_total_score(
|
|||
weight = 0.0
|
||||
|
||||
sem_w, edge_w, cent_w = _get_scoring_weights()
|
||||
total = (sem_w * float(semantic_score) * weight) + (edge_w * edge_bonus) + (cent_w * cent_bonus)
|
||||
status_mult = _get_status_multiplier(payload)
|
||||
|
||||
# Dynamic Edge Boosting
|
||||
# Wenn dynamische Boosts aktiv sind, erhöhen wir den Einfluss des Graphen
|
||||
# Dies ist eine Vereinfachung, da der echte Boost im Subgraph passiert sein sollte.
|
||||
final_edge_score = edge_w * edge_bonus
|
||||
if dynamic_edge_boosts and edge_bonus > 0:
|
||||
# Globaler Boost für Graph-Signale bei spezifischen Intents
|
||||
final_edge_score *= 1.2
|
||||
|
||||
total = (sem_w * float(semantic_score) * weight * status_mult) + final_edge_score + (cent_w * cent_bonus)
|
||||
return float(total), float(edge_bonus), float(cent_bonus)
|
||||
|
||||
|
||||
|
|
@ -138,10 +164,11 @@ def _build_explanation(
|
|||
except (TypeError, ValueError):
|
||||
type_weight = 1.0
|
||||
|
||||
status_mult = _get_status_multiplier(payload)
|
||||
note_type = payload.get("type", "unknown")
|
||||
|
||||
breakdown = ScoreBreakdown(
|
||||
semantic_contribution=(sem_w * semantic_score * type_weight),
|
||||
semantic_contribution=(sem_w * semantic_score * type_weight * status_mult),
|
||||
edge_contribution=(edge_w_cfg * edge_bonus),
|
||||
centrality_contribution=(cent_w_cfg * cent_bonus),
|
||||
raw_semantic=semantic_score,
|
||||
|
|
@ -162,6 +189,10 @@ def _build_explanation(
|
|||
msg = "Bevorzugt" if type_weight > 1.0 else "Leicht abgewertet"
|
||||
reasons.append(Reason(kind="type", message=f"{msg} aufgrund des Typs '{note_type}'.", score_impact=(sem_w * semantic_score * (type_weight - 1.0))))
|
||||
|
||||
if status_mult != 1.0:
|
||||
msg = "Status-Bonus" if status_mult > 1.0 else "Status-Malus"
|
||||
reasons.append(Reason(kind="lifecycle", message=f"{msg} ({payload.get('status')}).", score_impact=0.0))
|
||||
|
||||
if subgraph and node_key and edge_bonus > 0:
|
||||
if hasattr(subgraph, "get_outgoing_edges"):
|
||||
outgoing = subgraph.get_outgoing_edges(node_key)
|
||||
|
|
@ -226,6 +257,7 @@ def _build_hits_from_semantic(
|
|||
used_mode: str,
|
||||
subgraph: ga.Subgraph | None = None,
|
||||
explain: bool = False,
|
||||
dynamic_edge_boosts: Dict[str, float] = None
|
||||
) -> QueryResponse:
|
||||
"""Baut strukturierte QueryHits."""
|
||||
t0 = time.time()
|
||||
|
|
@ -246,7 +278,13 @@ def _build_hits_from_semantic(
|
|||
except Exception:
|
||||
cent_bonus = 0.0
|
||||
|
||||
total, edge_bonus, cent_bonus = _compute_total_score(semantic_score, payload, edge_bonus=edge_bonus, cent_bonus=cent_bonus)
|
||||
total, edge_bonus, cent_bonus = _compute_total_score(
|
||||
semantic_score,
|
||||
payload,
|
||||
edge_bonus=edge_bonus,
|
||||
cent_bonus=cent_bonus,
|
||||
dynamic_edge_boosts=dynamic_edge_boosts
|
||||
)
|
||||
enriched.append((pid, float(semantic_score), payload, total, edge_bonus, cent_bonus))
|
||||
|
||||
enriched_sorted = sorted(enriched, key=lambda h: h[3], reverse=True)
|
||||
|
|
@ -280,7 +318,6 @@ def _build_hits_from_semantic(
|
|||
"section": payload.get("section") or payload.get("section_title"),
|
||||
"text": text_content
|
||||
},
|
||||
# --- FIX: Wir füllen das payload-Feld explizit ---
|
||||
payload=payload,
|
||||
explanation=explanation_obj
|
||||
))
|
||||
|
|
@ -311,6 +348,10 @@ def hybrid_retrieve(req: QueryRequest) -> QueryResponse:
|
|||
hits = _semantic_hits(client, prefix, vector, top_k=top_k, filters=req.filters)
|
||||
|
||||
depth, edge_types = _extract_expand_options(req)
|
||||
|
||||
# WP-22: Dynamic Boosts aus dem Request (vom Router)
|
||||
boost_edges = getattr(req, "boost_edges", {})
|
||||
|
||||
subgraph: ga.Subgraph | None = None
|
||||
if depth and depth > 0:
|
||||
seed_ids: List[str] = []
|
||||
|
|
@ -320,11 +361,28 @@ def hybrid_retrieve(req: QueryRequest) -> QueryResponse:
|
|||
seed_ids.append(key)
|
||||
if seed_ids:
|
||||
try:
|
||||
# Hier könnten wir boost_edges auch an expand übergeben, wenn ga.expand es unterstützt
|
||||
subgraph = ga.expand(client, prefix, seed_ids, depth=depth, edge_types=edge_types)
|
||||
|
||||
# Manuelles Boosten der Kantengewichte im Graphen falls aktiv
|
||||
if boost_edges and subgraph and hasattr(subgraph, "graph"):
|
||||
for u, v, data in subgraph.graph.edges(data=True):
|
||||
k = data.get("kind")
|
||||
if k in boost_edges:
|
||||
# Gewicht erhöhen für diesen Query-Kontext
|
||||
data["weight"] = data.get("weight", 1.0) * boost_edges[k]
|
||||
|
||||
except Exception:
|
||||
subgraph = None
|
||||
|
||||
return _build_hits_from_semantic(hits, top_k=top_k, used_mode="hybrid", subgraph=subgraph, explain=req.explain)
|
||||
return _build_hits_from_semantic(
|
||||
hits,
|
||||
top_k=top_k,
|
||||
used_mode="hybrid",
|
||||
subgraph=subgraph,
|
||||
explain=req.explain,
|
||||
dynamic_edge_boosts=boost_edges
|
||||
)
|
||||
|
||||
|
||||
class Retriever:
|
||||
|
|
|
|||
109
app/services/edge_registry.py
Normal file
109
app/services/edge_registry.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
"""
|
||||
FILE: app/services/edge_registry.py
|
||||
DESCRIPTION: Single Source of Truth für Kanten-Typen. Parst '01_edge_vocabulary.md'.
|
||||
Implementiert WP-22 Teil B (Registry & Validation).
|
||||
"""
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, Optional, Set
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EdgeRegistry:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(EdgeRegistry, cls).__new__(cls)
|
||||
cls._instance.initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
if self.initialized: return
|
||||
# Pfad korrespondiert mit dem Frontmatter Pfad in 01_edge_vocabulary.md
|
||||
self.vocab_path = "01_User_Manual/01_edge_vocabulary.md"
|
||||
self.unknown_log_path = "data/logs/unknown_edges.jsonl"
|
||||
self.canonical_map: Dict[str, str] = {} # alias -> canonical
|
||||
self.valid_types: Set[str] = set()
|
||||
self._load_vocabulary()
|
||||
self.initialized = True
|
||||
|
||||
def _load_vocabulary(self):
|
||||
"""Parst die Markdown-Tabelle in 01_edge_vocabulary.md"""
|
||||
# Fallback Suche, falls das Skript aus Root oder app ausgeführt wird
|
||||
candidates = [
|
||||
self.vocab_path,
|
||||
os.path.join("..", self.vocab_path),
|
||||
"vault/01_User_Manual/01_edge_vocabulary.md"
|
||||
]
|
||||
|
||||
found_path = None
|
||||
for p in candidates:
|
||||
if os.path.exists(p):
|
||||
found_path = p
|
||||
break
|
||||
|
||||
if not found_path:
|
||||
logger.warning(f"Edge Vocabulary not found (checked: {candidates}). Registry empty.")
|
||||
return
|
||||
|
||||
# Regex für Tabellenzeilen: | **canonical** | alias, alias | ...
|
||||
# Matcht: | **caused_by** | ausgelöst_durch, wegen |
|
||||
pattern = re.compile(r"\|\s*\*\*([a-z_]+)\*\*\s*\|\s*([^|]+)\|")
|
||||
|
||||
try:
|
||||
with open(found_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
match = pattern.search(line)
|
||||
if match:
|
||||
canonical = match.group(1).strip()
|
||||
aliases_str = match.group(2).strip()
|
||||
|
||||
self.valid_types.add(canonical)
|
||||
self.canonical_map[canonical] = canonical # Self-ref
|
||||
|
||||
# Aliases parsen
|
||||
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:
|
||||
# Clean up user inputs (e.g. remove backticks if present)
|
||||
clean_alias = alias.replace("`", "")
|
||||
self.canonical_map[clean_alias] = canonical
|
||||
|
||||
logger.info(f"EdgeRegistry loaded: {len(self.valid_types)} canonical types from {found_path}.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to parse Edge Vocabulary: {e}")
|
||||
|
||||
def resolve(self, edge_type: str) -> str:
|
||||
"""
|
||||
Normalisiert Kanten-Typen. Loggt unbekannte Typen, verwirft sie aber nicht (Learning System).
|
||||
"""
|
||||
if not edge_type:
|
||||
return "related_to"
|
||||
|
||||
clean_type = edge_type.lower().strip().replace(" ", "_")
|
||||
|
||||
# 1. Lookup
|
||||
if clean_type in self.canonical_map:
|
||||
return self.canonical_map[clean_type]
|
||||
|
||||
# 2. Unknown Handling
|
||||
self._log_unknown(clean_type)
|
||||
return clean_type # Pass-through (nicht verwerfen, aber loggen)
|
||||
|
||||
def _log_unknown(self, edge_type: str):
|
||||
"""Schreibt unbekannte Typen in ein Append-Only Log für Review."""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self.unknown_log_path), exist_ok=True)
|
||||
# Einfaches JSONL Format
|
||||
entry = {"unknown_type": edge_type, "status": "new"}
|
||||
with open(self.unknown_log_path, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(entry) + "\n")
|
||||
except Exception:
|
||||
pass # Silent fail bei Logging, darf Ingestion nicht stoppen
|
||||
|
||||
# Singleton Accessor
|
||||
registry = EdgeRegistry()
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
---
|
||||
doc_type: reference
|
||||
audience: author
|
||||
status: active
|
||||
context: "Zentrales Wörterbuch für Kanten-Bezeichner (Edges). Referenz für Autoren zur Wahl des richtigen Verbindungstyps."
|
||||
---
|
||||
|
||||
# Edge Vocabulary & Semantik
|
||||
|
||||
**Standort:** `01_User_Manual/01_edge_vocabulary.md`
|
||||
**Zweck:** Damit Mindnet "verstehen" kann, was eine Verbindung bedeutet (z.B. für "Warum"-Fragen), nutzen wir konsistente Begriffe.
|
||||
|
||||
**Die Goldene Regel:**
|
||||
Nutze bevorzugt den **System-Typ** (linke Spalte). Wenn dieser sprachlich nicht passt, nutze einen der **erlaubten Aliasse** (und bleibe dabei konsistent).
|
||||
|
||||
---
|
||||
|
||||
## 1. Kausalität & Logik ("Warum?")
|
||||
*Verbindungen, die Gründe, Ursachen oder Herleitungen beschreiben.*
|
||||
|
||||
| System-Typ (Canonical) | Erlaubte Aliasse (User) | Wann nutzen? |
|
||||
| :--- | :--- | :--- |
|
||||
| **`caused_by`** | `ausgelöst_durch`, `wegen`, `ursache_ist` | Wenn A der direkte Auslöser für B ist. (Technisch/Faktisch) |
|
||||
| **`derived_from`** | `abgeleitet_von`, `quelle`, `inspiriert_durch` | Wenn eine Idee/Prinzip aus einer Quelle stammt (z.B. Buch, Zitat). |
|
||||
| **`based_on`** | `basiert_auf`, `fundament`, `grundlage` | Wenn B nicht existieren könnte ohne das fundamentale Konzept A (z.B. Werte). |
|
||||
| **`solves`** | `löst`, `beantwortet`, `fix_für` | Wenn A eine Lösung für das Problem B ist. |
|
||||
|
||||
## 2. Struktur & Hierarchie ("Wo gehört es hin?")
|
||||
*Verbindungen, die Ordnung schaffen.*
|
||||
|
||||
| System-Typ (Canonical) | Erlaubte Aliasse (User) | Wann nutzen? |
|
||||
| :--- | :--- | :--- |
|
||||
| **`part_of`** | `teil_von`, `gehört_zu`, `cluster` | Harte Hierarchie. Projekt A ist Teil von Programm B. |
|
||||
| **`belongs_to`** | *(System-Intern)* | Automatisch generiert (Chunk -> Note). Nicht manuell nutzen. |
|
||||
|
||||
## 3. Abhängigkeit & Einfluss ("Was brauche ich?")
|
||||
*Verbindungen, die Blockaden oder Voraussetzungen definieren.*
|
||||
|
||||
| System-Typ (Canonical) | Erlaubte Aliasse (User) | Wann nutzen? |
|
||||
| :--- | :--- | :--- |
|
||||
| **`depends_on`** | `hängt_ab_von`, `braucht`, `requires`, `enforced_by` | Harte Abhängigkeit. Ohne A kann B nicht starten/existieren. |
|
||||
| **`blocks`** | `blockiert`, `verhindert`, `risiko_für` | Wenn A ein aktives Hindernis für B ist. |
|
||||
| **`uses`** | `nutzt`, `verwendet`, `tool` | Wenn A ein Werkzeug/Methode B einsetzt (z.B. Projekt nutzt Python). |
|
||||
| **`guides`** | `steuert`, `leitet`, `orientierung` | (Alias für `depends_on` oder `related_to`) Weiche Abhängigkeit, z.B. Prinzipien steuern Verhalten. |
|
||||
|
||||
## 4. Zeit & Prozess ("Was kommt dann?")
|
||||
*Verbindungen für Abläufe zwischen verschiedenen Notizen.*
|
||||
|
||||
| System-Typ (Canonical) | Erlaubte Aliasse (User) | Wann nutzen? |
|
||||
| :--- | :--- | :--- |
|
||||
| **`next`** | `danach`, `folgt`, `nachfolger`, `followed_by` | Manuell: Wenn Notiz A logisch vor Notiz B kommt (Prozesskette: A -> B). |
|
||||
| **`prev`** | `davor`, `vorgänger`, `preceded_by` | Manuell: Der vorherige Prozessschritt (B -> A). |
|
||||
|
||||
## 5. Assoziation ("Was ist ähnlich?")
|
||||
*Lose Verbindungen für Entdeckungen.*
|
||||
|
||||
| System-Typ (Canonical) | Erlaubte Aliasse (User) | Wann nutzen? |
|
||||
| :--- | :--- | :--- |
|
||||
| **`related_to`** | `siehe_auch`, `kontext`, `thematisch` | Wenn zwei Dinge etwas miteinander zu tun haben, ohne strikte Logik. |
|
||||
| **`similar_to`** | `ähnlich_wie`, `vergleichbar` | Wenn A und B fast das Gleiche sind (z.B. Synonyme, Alternativen). |
|
||||
| **`references`** | *(Kein Alias)* | Standard für "erwähnt einfach nur". (Niedrigste Prio). |
|
||||
|
||||
---
|
||||
|
||||
## Best Practices für Autoren
|
||||
|
||||
### A. Der "Callout"-Standard (Footer)
|
||||
Sammle Verbindungen, die für die **ganze Notiz** gelten, am Ende der Datei in einem Bereich `## Kontext & Verbindungen`.
|
||||
|
||||
```markdown
|
||||
## Kontext & Verbindungen
|
||||
|
||||
> [!edge] derived_from
|
||||
> [[Buch der Weisen]]
|
||||
|
||||
> [!edge] part_of
|
||||
> [[Leitbild – Identity Core]]
|
||||
```
|
||||
|
||||
### B. Der "Inline"-Standard (Präzision)
|
||||
Nutze Inline-Kanten nur, wenn du im Fließtext eine **spezifische Aussage** triffst, die im Graph sichtbar sein soll.
|
||||
|
||||
* *Schlecht:* "Das [[rel:related_to Projekt]] ist wichtig." (Wenig Aussagekraft)
|
||||
* *Gut:* "Dieser Fehler wird [[rel:caused_by Systemabsturz]] verursacht." (Starke Kausalität)
|
||||
|
||||
### C. Konsistenz-Check
|
||||
Bevor du ein neues Wort (z.B. `ermöglicht`) nutzt:
|
||||
1. Prüfe diese Liste: Passt `solves` oder `depends_on`?
|
||||
2. Falls nein: Schreibe `ermöglicht` in die Spalte "Erlaubte Aliasse" oben und ordne es einem System-Typ zu.
|
||||
|
|
@ -47,7 +47,7 @@ Eine Übersicht der implementierten Features zum schnellen Auffinden von Funktio
|
|||
| **WP-19** | Graph Visualisierung | **Frontend Modularisierung:** Umbau auf `ui_*.py`.<br>**Graph Engines:** Parallelbetrieb von Cytoscape (COSE) und Agraph.<br>**Tools:** "Single Source of Truth" Editor, Persistenz via URL. |
|
||||
| **WP-20** | Cloud Hybrid Mode | Nutzung von Public LLM für schnellere Verarbeitung und bestimmte Aufgaben |
|
||||
| **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 & Meta-Configuration | Professionalisierung der Datenhaltung durch Einführung eines Lebenszyklus (Status) und "Docs-as-Code" Konfiguration. |
|
||||
---
|
||||
|
||||
## 3. Offene Workpackages (Planung)
|
||||
|
|
@ -123,6 +123,23 @@ Diese Features stehen als nächstes an oder befinden sich in der Umsetzung.
|
|||
3. **Graph Reasoning:**
|
||||
* Der Retriever priorisiert Pfade, die dem semantischen Muster der Frage entsprechen, nicht nur dem Text-Match.
|
||||
|
||||
|
||||
### WP-22 – Content Lifecycle & Meta-Configuration
|
||||
**Status:** 🟡 Geplant
|
||||
**Ziel:** Professionalisierung der Datenhaltung durch Einführung eines Lebenszyklus (Status) und "Docs-as-Code" Konfiguration.
|
||||
**Problem:**
|
||||
1. **Müll im Index:** Unfertige Ideen (`draft`) oder System-Dateien (`templates`) verschmutzen die Suchergebnisse.
|
||||
2. **Redundanz:** Kanten-Typen müssen in Python-Code und Obsidian-Skripten doppelt gepflegt werden.
|
||||
|
||||
**Lösungsansätze:**
|
||||
1. **Status-Logik (Frontmatter):**
|
||||
* `status: system` / `template` → **Hard Skip** im Importer (Kein Index).
|
||||
* `status: draft` vs. `stable` → **Scoring Modifier** im Retriever (Stabiles Wissen wird bevorzugt).
|
||||
2. **Single Source of Truth (SSOT):**
|
||||
* Die Datei `01_edge_vocabulary.md` wird zur führenden Konfiguration.
|
||||
* Eine neue `Registry`-Klasse liest diese Markdown-Datei beim Start und validiert Kanten dagegen.
|
||||
3. **Self-Learning Loop:**
|
||||
* Unbekannte Kanten-Typen (vom User neu erfunden) werden nicht verworfen, sondern in ein `unknown_edges.jsonl` Log geschrieben, um das Vokabular organisch zu erweitern.
|
||||
---
|
||||
|
||||
## 4. Abhängigkeiten & Release-Plan
|
||||
|
|
|
|||
|
|
@ -254,3 +254,65 @@ 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
|
||||
|
||||
**Status:** 🚀 Startklar
|
||||
**Fokus:** Ingestion-Filter, Scoring-Logik, Registry-Architektur.
|
||||
|
||||
```text
|
||||
Du bist der Lead Architect für "Mindnet" (v2.7).
|
||||
Wir starten ein umfassendes Architektur-Paket: **WP-22: Graph Intelligence & Lifecycle**.
|
||||
|
||||
**Kontext:**
|
||||
Wir professionalisieren die Datenhaltung ("Docs as Code") und machen die Suche kontextsensitiv.
|
||||
Wir haben eine Markdown-Datei (`01_edge_vocabulary.md`), die als Single-Source-of-Truth für Kanten-Typen dient.
|
||||
|
||||
**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):**
|
||||
* Wenn `status` in `['system', 'template']`: **Hard Skip**. Datei wird nicht vektorisiert.
|
||||
2. **Wissens-Status (Scoring):**
|
||||
* 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).
|
||||
* Liest beim Start `01_User_Manual/01_edge_vocabulary.md` (Regex parsing der Tabelle).
|
||||
* Stellt `canonical_types` und `aliases` bereit.
|
||||
2. **Rückwärtslogik (Learning):**
|
||||
* 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").
|
||||
|
||||
**Architektur-Vorgabe (WICHTIG):**
|
||||
Die Gewichtung findet **Pre-Retrieval** (im Scoring-Algorithmus) statt, **nicht** im LLM-Prompt.
|
||||
|
||||
1. **Decision Engine (`decision_engine.yaml`):**
|
||||
* Füge `boost_edges` zu Strategien hinzu.
|
||||
* *Beispiel:* `EXPLANATION` (Warum-Fragen) -> Boost `caused_by: 2.5`, `derived_from: 2.0`.
|
||||
* *Beispiel:* `ACTION` (Wie-Fragen) -> Boost `next: 3.0`, `depends_on: 2.0`.
|
||||
2. **Scoring-Logik (`scoring.py`):**
|
||||
* 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).
|
||||
2. Zeige die Integration in `ingestion.py` (Status-Filter & Edge-Validierung).
|
||||
3. Zeige die Erweiterung in `scoring.py` (Status-Gewicht & Dynamic Edge Boosting).
|
||||
|
||||
Bitte bestätige die Übernahme dieses Architektur-Pakets.
|
||||
30
vault_master/01_User_Manual/01_edge_vocabulary.md
Normal file
30
vault_master/01_User_Manual/01_edge_vocabulary.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
id: edge_vocabulary
|
||||
title: Edge Vocabulary & Semantik
|
||||
type: reference
|
||||
status: system
|
||||
system_role: config
|
||||
context: "Zentrales Wörterbuch für Kanten-Bezeichner. Dient als Single Source of Truth für Obsidian-Skripte und Mindnet-Validierung."
|
||||
---
|
||||
|
||||
# Edge Vocabulary & Semantik
|
||||
|
||||
**Pfad:** `01_User_Manual/01_edge_vocabulary.md`
|
||||
**Zweck:** Definition aller erlaubten Kanten-Typen und ihrer Aliase.
|
||||
|
||||
| System-Typ (Canonical) | Erlaubte Aliasse (User) | Beschreibung |
|
||||
| :--- | :--- | :--- |
|
||||
| **`caused_by`** | `ausgelöst_durch`, `wegen`, `ursache_ist` | Kausalität: A löst B aus. |
|
||||
| **`derived_from`** | `abgeleitet_von`, `quelle`, `inspiriert_durch` | Herkunft: A stammt von B. |
|
||||
| **`based_on`** | `basiert_auf`, `fundament`, `grundlage` | Fundament: B baut auf A auf. |
|
||||
| **`solves`** | `löst`, `beantwortet`, `fix_für` | Lösung: A ist Lösung für Problem B. |
|
||||
| **`part_of`** | `teil_von`, `gehört_zu`, `cluster` | Hierarchie: Kind -> Eltern. |
|
||||
| **`depends_on`** | `hängt_ab_von`, `braucht`, `requires`, `enforced_by` | Abhängigkeit: A braucht B. |
|
||||
| **`blocks`** | `blockiert`, `verhindert`, `risiko_für` | Blocker: A verhindert B. |
|
||||
| **`uses`** | `nutzt`, `verwendet`, `tool` | Werkzeug: A nutzt B. |
|
||||
| **`guides`** | `steuert`, `leitet`, `orientierung` | Soft-Dependency: A gibt Richtung für B. |
|
||||
| **`next`** | `danach`, `folgt`, `nachfolger`, `followed_by` | Prozess: A -> B. |
|
||||
| **`prev`** | `davor`, `vorgänger`, `preceded_by` | Prozess: B <- A. |
|
||||
| **`related_to`** | `siehe_auch`, `kontext`, `thematisch` | Lose Assoziation. |
|
||||
| **`similar_to`** | `ähnlich_wie`, `vergleichbar` | Synonym / Ähnlichkeit. |
|
||||
| **`references`** | *(Kein Alias)* | Standard-Verweis (Fallback). |
|
||||
Loading…
Reference in New Issue
Block a user