WP24c - Agentic Edge Validation & Chunk-Aware Multigraph-System (v4.5.8) #22
|
|
@ -65,9 +65,32 @@ def get_note_scope_zone_headers() -> List[str]:
|
||||||
]
|
]
|
||||||
return header_list
|
return header_list
|
||||||
|
|
||||||
|
# WP-24c v4.5.6: Header-basierte Identifikation von LLM-Validierungs-Zonen
|
||||||
|
# Konfigurierbar via MINDNET_LLM_VALIDATION_HEADERS (komma-separiert)
|
||||||
|
def get_llm_validation_zone_headers() -> List[str]:
|
||||||
|
"""
|
||||||
|
Lädt die konfigurierten Header-Namen für LLM-Validierungs-Zonen.
|
||||||
|
Fallback auf Defaults, falls nicht konfiguriert.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
headers_env = os.getenv(
|
||||||
|
"MINDNET_LLM_VALIDATION_HEADERS",
|
||||||
|
"Unzugeordnete Kanten,Edge Pool,Candidates"
|
||||||
|
)
|
||||||
|
header_list = [h.strip() for h in headers_env.split(",") if h.strip()]
|
||||||
|
# Fallback auf Defaults, falls leer
|
||||||
|
if not header_list:
|
||||||
|
header_list = [
|
||||||
|
"Unzugeordnete Kanten",
|
||||||
|
"Edge Pool",
|
||||||
|
"Candidates"
|
||||||
|
]
|
||||||
|
return header_list
|
||||||
|
|
||||||
def extract_note_scope_zones(markdown_body: str) -> List[Tuple[str, str]]:
|
def extract_note_scope_zones(markdown_body: str) -> List[Tuple[str, str]]:
|
||||||
"""
|
"""
|
||||||
WP-24c v4.2.0: Extrahiert Note-Scope Zonen aus Markdown.
|
WP-24c v4.2.0: Extrahiert Note-Scope Zonen aus Markdown.
|
||||||
|
WP-24c v4.5.6: Unterscheidet zwischen Note-Scope-Zonen und LLM-Validierungs-Zonen.
|
||||||
|
|
||||||
Identifiziert Sektionen mit spezifischen Headern (konfigurierbar via .env)
|
Identifiziert Sektionen mit spezifischen Headern (konfigurierbar via .env)
|
||||||
und extrahiert alle darin enthaltenen Links.
|
und extrahiert alle darin enthaltenen Links.
|
||||||
|
|
@ -93,21 +116,30 @@ def extract_note_scope_zones(markdown_body: str) -> List[Tuple[str, str]]:
|
||||||
in_zone = False
|
in_zone = False
|
||||||
zone_content = []
|
zone_content = []
|
||||||
|
|
||||||
|
# WP-24c v4.5.6: Lade beide Header-Listen für Unterscheidung
|
||||||
|
zone_headers = get_note_scope_zone_headers()
|
||||||
|
llm_validation_headers = get_llm_validation_zone_headers()
|
||||||
|
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
# Prüfe auf Header
|
# Prüfe auf Header
|
||||||
header_match = re.match(header_pattern, line.strip())
|
header_match = re.match(header_pattern, line.strip())
|
||||||
if header_match:
|
if header_match:
|
||||||
header_text = header_match.group(1).strip()
|
header_text = header_match.group(1).strip()
|
||||||
|
|
||||||
# Prüfe, ob dieser Header eine Note-Scope Zone ist
|
# WP-24c v4.5.6: Prüfe, ob dieser Header eine Note-Scope Zone ist
|
||||||
# WP-24c v4.2.0: Dynamisches Laden der konfigurierten Header
|
# (NICHT eine LLM-Validierungs-Zone - diese werden separat behandelt)
|
||||||
zone_headers = get_note_scope_zone_headers()
|
|
||||||
is_zone_header = any(
|
is_zone_header = any(
|
||||||
header_text.lower() == zone_header.lower()
|
header_text.lower() == zone_header.lower()
|
||||||
for zone_header in zone_headers
|
for zone_header in zone_headers
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_zone_header:
|
# WP-24c v4.5.6: Ignoriere LLM-Validierungs-Zonen hier (werden separat verarbeitet)
|
||||||
|
is_llm_validation = any(
|
||||||
|
header_text.lower() == llm_header.lower()
|
||||||
|
for llm_header in llm_validation_headers
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_zone_header and not is_llm_validation:
|
||||||
in_zone = True
|
in_zone = True
|
||||||
zone_content = []
|
zone_content = []
|
||||||
continue
|
continue
|
||||||
|
|
@ -143,6 +175,90 @@ def extract_note_scope_zones(markdown_body: str) -> List[Tuple[str, str]]:
|
||||||
|
|
||||||
return edges
|
return edges
|
||||||
|
|
||||||
|
def extract_llm_validation_zones(markdown_body: str) -> List[Tuple[str, str]]:
|
||||||
|
"""
|
||||||
|
WP-24c v4.5.6: Extrahiert LLM-Validierungs-Zonen aus Markdown.
|
||||||
|
|
||||||
|
Identifiziert Sektionen mit LLM-Validierungs-Headern (konfigurierbar via .env)
|
||||||
|
und extrahiert alle darin enthaltenen Links (Wikilinks, Typed Relations, Callouts).
|
||||||
|
Diese Kanten erhalten das Präfix "candidate:" in der rule_id.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Tuple[str, str]]: Liste von (kind, target) Tupeln
|
||||||
|
"""
|
||||||
|
if not markdown_body:
|
||||||
|
return []
|
||||||
|
|
||||||
|
edges: List[Tuple[str, str]] = []
|
||||||
|
|
||||||
|
# WP-24c v4.5.6: Konfigurierbare Header-Ebene für LLM-Validierung
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
llm_validation_level = int(os.getenv("MINDNET_LLM_VALIDATION_HEADER_LEVEL", "3"))
|
||||||
|
header_level_pattern = "#" * llm_validation_level
|
||||||
|
|
||||||
|
# Regex für Header-Erkennung (konfigurierbare Ebene)
|
||||||
|
header_pattern = rf'^{re.escape(header_level_pattern)}\s+(.+?)$'
|
||||||
|
|
||||||
|
lines = markdown_body.split('\n')
|
||||||
|
in_zone = False
|
||||||
|
zone_content = []
|
||||||
|
|
||||||
|
# WP-24c v4.5.6: Lade LLM-Validierungs-Header
|
||||||
|
llm_validation_headers = get_llm_validation_zone_headers()
|
||||||
|
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
# Prüfe auf Header
|
||||||
|
header_match = re.match(header_pattern, line.strip())
|
||||||
|
if header_match:
|
||||||
|
header_text = header_match.group(1).strip()
|
||||||
|
|
||||||
|
# WP-24c v4.5.6: Prüfe, ob dieser Header eine LLM-Validierungs-Zone ist
|
||||||
|
is_llm_validation = any(
|
||||||
|
header_text.lower() == llm_header.lower()
|
||||||
|
for llm_header in llm_validation_headers
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_llm_validation:
|
||||||
|
in_zone = True
|
||||||
|
zone_content = []
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Neuer Header gefunden, der keine Zone ist -> Zone beendet
|
||||||
|
if in_zone:
|
||||||
|
# Verarbeite gesammelten Inhalt
|
||||||
|
zone_text = '\n'.join(zone_content)
|
||||||
|
# Extrahiere Typed Relations
|
||||||
|
typed, _ = extract_typed_relations(zone_text)
|
||||||
|
edges.extend(typed)
|
||||||
|
# Extrahiere Wikilinks (als related_to)
|
||||||
|
wikilinks = extract_wikilinks(zone_text)
|
||||||
|
for wl in wikilinks:
|
||||||
|
edges.append(("related_to", wl))
|
||||||
|
# WP-24c v4.5.6: Extrahiere auch Callouts aus LLM-Validierungs-Zonen
|
||||||
|
callout_pairs, _ = extract_callout_relations(zone_text)
|
||||||
|
edges.extend(callout_pairs)
|
||||||
|
in_zone = False
|
||||||
|
zone_content = []
|
||||||
|
|
||||||
|
# Sammle Inhalt, wenn wir in einer Zone sind
|
||||||
|
if in_zone:
|
||||||
|
zone_content.append(line)
|
||||||
|
|
||||||
|
# Verarbeite letzte Zone (falls am Ende des Dokuments)
|
||||||
|
if in_zone and zone_content:
|
||||||
|
zone_text = '\n'.join(zone_content)
|
||||||
|
typed, _ = extract_typed_relations(zone_text)
|
||||||
|
edges.extend(typed)
|
||||||
|
wikilinks = extract_wikilinks(zone_text)
|
||||||
|
for wl in wikilinks:
|
||||||
|
edges.append(("related_to", wl))
|
||||||
|
# WP-24c v4.5.6: Extrahiere auch Callouts aus LLM-Validierungs-Zonen
|
||||||
|
callout_pairs, _ = extract_callout_relations(zone_text)
|
||||||
|
edges.extend(callout_pairs)
|
||||||
|
|
||||||
|
return edges
|
||||||
|
|
||||||
def extract_callouts_from_markdown(
|
def extract_callouts_from_markdown(
|
||||||
markdown_body: str,
|
markdown_body: str,
|
||||||
note_id: str,
|
note_id: str,
|
||||||
|
|
@ -249,7 +365,9 @@ def build_edges_for_note(
|
||||||
note_type = _get(chunks[0], "type") if chunks else "concept"
|
note_type = _get(chunks[0], "type") if chunks else "concept"
|
||||||
|
|
||||||
# WP-24c v4.2.0: Note-Scope Zonen Extraktion (VOR Chunk-Verarbeitung)
|
# WP-24c v4.2.0: Note-Scope Zonen Extraktion (VOR Chunk-Verarbeitung)
|
||||||
|
# WP-24c v4.5.6: Separate Behandlung von LLM-Validierungs-Zonen
|
||||||
note_scope_edges: List[dict] = []
|
note_scope_edges: List[dict] = []
|
||||||
|
llm_validation_edges: List[dict] = []
|
||||||
|
|
||||||
if markdown_body:
|
if markdown_body:
|
||||||
# 1. Note-Scope Zonen (Wikilinks und Typed Relations)
|
# 1. Note-Scope Zonen (Wikilinks und Typed Relations)
|
||||||
|
|
@ -280,6 +398,55 @@ def build_edges_for_note(
|
||||||
extra=payload
|
extra=payload
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# WP-24c v4.5.6: LLM-Validierungs-Zonen (mit candidate: Präfix)
|
||||||
|
llm_validation_links = extract_llm_validation_zones(markdown_body)
|
||||||
|
for kind, raw_target in llm_validation_links:
|
||||||
|
target, sec = parse_link_target(raw_target, note_id)
|
||||||
|
if not target:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# WP-24c v4.5.6: LLM-Validierungs-Kanten mit scope: "note" und rule_id: "candidate:..."
|
||||||
|
# Diese werden gegen alle Chunks der Note geprüft
|
||||||
|
# Bestimme Provenance basierend auf Link-Typ
|
||||||
|
if kind == "related_to":
|
||||||
|
# Wikilink in LLM-Validierungs-Zone
|
||||||
|
provenance = "explicit:wikilink"
|
||||||
|
else:
|
||||||
|
# Typed Relation oder Callout in LLM-Validierungs-Zone
|
||||||
|
provenance = "explicit"
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"edge_id": _mk_edge_id(kind, note_id, target, "note", target_section=sec),
|
||||||
|
"provenance": provenance,
|
||||||
|
"rule_id": f"candidate:{provenance}", # WP-24c v4.5.6: Zonen-Priorität - candidate: Präfix
|
||||||
|
"confidence": PROVENANCE_PRIORITY.get(provenance, 0.90)
|
||||||
|
}
|
||||||
|
if sec:
|
||||||
|
payload["target_section"] = sec
|
||||||
|
|
||||||
|
llm_validation_edges.append(_edge(
|
||||||
|
kind=kind,
|
||||||
|
scope="note",
|
||||||
|
source_id=note_id, # WP-24c v4.5.6: source_id = note_id (Note-Scope für LLM-Validierung)
|
||||||
|
target_id=target,
|
||||||
|
note_id=note_id,
|
||||||
|
extra=payload
|
||||||
|
))
|
||||||
|
|
||||||
|
# WP-24c v4.5.6: Füge Callouts aus LLM-Validierungs-Zonen zu all_chunk_callout_keys hinzu
|
||||||
|
# damit sie nicht im globalen Scan doppelt verarbeitet werden
|
||||||
|
# (Nur für Callouts, nicht für Wikilinks oder Typed Relations)
|
||||||
|
# Callouts werden in extract_llm_validation_zones bereits extrahiert
|
||||||
|
# und müssen daher aus dem globalen Scan ausgeschlossen werden
|
||||||
|
# Hinweis: extract_llm_validation_zones gibt auch Callouts zurück (als (kind, target) Tupel)
|
||||||
|
# Daher müssen wir prüfen, ob es sich um einen Callout handelt
|
||||||
|
# (Callouts haben typischerweise spezifische kinds wie "depends_on", "related_to", etc.)
|
||||||
|
# Für jetzt nehmen wir an, dass alle Links aus LLM-Validierungs-Zonen als "bereits verarbeitet" markiert werden
|
||||||
|
# Dies verhindert Duplikate im globalen Scan
|
||||||
|
callout_key = (kind, target, sec)
|
||||||
|
all_chunk_callout_keys.add(callout_key)
|
||||||
|
logger.debug(f"Note [{note_id}]: LLM-Validierungs-Zone Callout-Key hinzugefügt: ({kind}, {target}, {sec})")
|
||||||
|
|
||||||
# 1) Struktur-Kanten (Internal: belongs_to, next/prev)
|
# 1) Struktur-Kanten (Internal: belongs_to, next/prev)
|
||||||
# Diese erhalten die Provenienz 'structure' und sind in der Registry geschützt.
|
# Diese erhalten die Provenienz 'structure' und sind in der Registry geschützt.
|
||||||
for idx, ch in enumerate(chunks):
|
for idx, ch in enumerate(chunks):
|
||||||
|
|
@ -400,11 +567,23 @@ def build_edges_for_note(
|
||||||
if t:
|
if t:
|
||||||
# WP-24c v4.1.0: target_section fließt nun fest in die ID-Generierung ein
|
# WP-24c v4.1.0: target_section fließt nun fest in die ID-Generierung ein
|
||||||
# WP-24c v4.3.1: explicit:callout erhält Confidence 1.0 für Präzisions-Priorität
|
# WP-24c v4.3.1: explicit:callout erhält Confidence 1.0 für Präzisions-Priorität
|
||||||
|
# WP-24c v4.5.6: candidate: Präfix NUR für global_pool (aus LLM-Validierungs-Zonen)
|
||||||
|
# Normale Callouts im Fließtext erhalten KEIN candidate: Präfix
|
||||||
confidence = 1.0 if p == "explicit:callout" else PROVENANCE_PRIORITY.get(p, 0.90)
|
confidence = 1.0 if p == "explicit:callout" else PROVENANCE_PRIORITY.get(p, 0.90)
|
||||||
|
|
||||||
|
# WP-24c v4.5.6: rule_id nur mit candidate: für global_pool (LLM-Validierungs-Zonen)
|
||||||
|
# explicit:callout (normale Callouts im Fließtext) erhalten KEIN candidate: Präfix
|
||||||
|
if p == "global_pool":
|
||||||
|
rule_id = f"candidate:{p}"
|
||||||
|
elif p == "explicit:callout":
|
||||||
|
rule_id = "explicit:callout" # WP-24c v4.5.6: Kein candidate: für Fließtext-Callouts
|
||||||
|
else:
|
||||||
|
rule_id = p # Andere Provenances ohne candidate:
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"chunk_id": cid,
|
"chunk_id": cid,
|
||||||
"edge_id": _mk_edge_id(k, cid, t, "chunk", target_section=sec),
|
"edge_id": _mk_edge_id(k, cid, t, "chunk", target_section=sec),
|
||||||
"provenance": p, "rule_id": f"candidate:{p}", "confidence": confidence
|
"provenance": p, "rule_id": rule_id, "confidence": confidence
|
||||||
}
|
}
|
||||||
if sec: payload["target_section"] = sec
|
if sec: payload["target_section"] = sec
|
||||||
edges.append(_edge(k, "chunk", cid, t, note_id, payload))
|
edges.append(_edge(k, "chunk", cid, t, note_id, payload))
|
||||||
|
|
@ -483,7 +662,10 @@ def build_edges_for_note(
|
||||||
}))
|
}))
|
||||||
|
|
||||||
# 4) WP-24c v4.2.0: Note-Scope Edges hinzufügen (VOR De-Duplizierung)
|
# 4) WP-24c v4.2.0: Note-Scope Edges hinzufügen (VOR De-Duplizierung)
|
||||||
|
# WP-24c v4.2.0: Note-Scope Edges hinzufügen
|
||||||
edges.extend(note_scope_edges)
|
edges.extend(note_scope_edges)
|
||||||
|
# WP-24c v4.5.6: LLM-Validierungs-Edges hinzufügen (mit candidate: Präfix)
|
||||||
|
edges.extend(llm_validation_edges)
|
||||||
|
|
||||||
# 5) WP-24c v4.2.9 Fix B PHASE 2 (Deduplizierung): Callout-Extraktion aus Markdown
|
# 5) WP-24c v4.2.9 Fix B PHASE 2 (Deduplizierung): Callout-Extraktion aus Markdown
|
||||||
# Der globale Scan des markdown_body nutzt all_chunk_callout_keys als Ausschlusskriterium.
|
# Der globale Scan des markdown_body nutzt all_chunk_callout_keys als Ausschlusskriterium.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user