WP24c - Agentic Edge Validation & Chunk-Aware Multigraph-System (v4.5.8) #22
|
|
@ -9,11 +9,15 @@ DESCRIPTION: Hauptlogik zur Kanten-Aggregation und De-Duplizierung.
|
||||||
- Header-basierte Identifikation von Note-Scope Zonen
|
- Header-basierte Identifikation von Note-Scope Zonen
|
||||||
- Automatische Scope-Umschaltung (chunk -> note)
|
- Automatische Scope-Umschaltung (chunk -> note)
|
||||||
- Priorisierung: Note-Scope Links haben Vorrang bei Duplikaten
|
- Priorisierung: Note-Scope Links haben Vorrang bei Duplikaten
|
||||||
VERSION: 4.2.0 (WP-24c: Note-Scope Zones)
|
WP-24c v4.2.1: Clean-Context Bereinigung
|
||||||
|
- Konsolidierte Callout-Extraktion (keine Duplikate)
|
||||||
|
- Smart Scope-Priorisierung (chunk bevorzugt, außer bei höherer Provenance)
|
||||||
|
- Effiziente Verarbeitung ohne redundante Scans
|
||||||
|
VERSION: 4.2.1 (WP-24c: Clean-Context Bereinigung)
|
||||||
STATUS: Active
|
STATUS: Active
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from typing import List, Optional, Dict, Tuple
|
from typing import List, Optional, Dict, Tuple, Set
|
||||||
from .graph_utils import (
|
from .graph_utils import (
|
||||||
_get, _edge, _mk_edge_id, _dedupe_seq, parse_link_target,
|
_get, _edge, _mk_edge_id, _dedupe_seq, parse_link_target,
|
||||||
PROVENANCE_PRIORITY, load_types_registry, get_edge_defaults_for
|
PROVENANCE_PRIORITY, load_types_registry, get_edge_defaults_for
|
||||||
|
|
@ -104,9 +108,7 @@ def extract_note_scope_zones(markdown_body: str) -> List[Tuple[str, str]]:
|
||||||
wikilinks = extract_wikilinks(zone_text)
|
wikilinks = extract_wikilinks(zone_text)
|
||||||
for wl in wikilinks:
|
for wl in wikilinks:
|
||||||
edges.append(("related_to", wl))
|
edges.append(("related_to", wl))
|
||||||
# Extrahiere Callouts
|
# WP-24c v4.2.1: Callouts werden NICHT hier extrahiert, da sie global abgedeckt werden
|
||||||
callouts, _ = extract_callout_relations(zone_text)
|
|
||||||
edges.extend(callouts)
|
|
||||||
in_zone = False
|
in_zone = False
|
||||||
zone_content = []
|
zone_content = []
|
||||||
|
|
||||||
|
|
@ -122,8 +124,71 @@ def extract_note_scope_zones(markdown_body: str) -> List[Tuple[str, str]]:
|
||||||
wikilinks = extract_wikilinks(zone_text)
|
wikilinks = extract_wikilinks(zone_text)
|
||||||
for wl in wikilinks:
|
for wl in wikilinks:
|
||||||
edges.append(("related_to", wl))
|
edges.append(("related_to", wl))
|
||||||
callouts, _ = extract_callout_relations(zone_text)
|
# WP-24c v4.2.1: Callouts werden NICHT hier extrahiert, da sie global abgedeckt werden
|
||||||
edges.extend(callouts)
|
|
||||||
|
return edges
|
||||||
|
|
||||||
|
def extract_callouts_from_markdown(
|
||||||
|
markdown_body: str,
|
||||||
|
note_id: str,
|
||||||
|
existing_chunk_callouts: Optional[Set[Tuple[str, str, Optional[str]]]] = None
|
||||||
|
) -> List[dict]:
|
||||||
|
"""
|
||||||
|
WP-24c v4.2.1: Extrahiert Callouts aus dem Original-Markdown.
|
||||||
|
|
||||||
|
Smart Logic: Nur Callouts, die NICHT in Chunks vorkommen (z.B. in Edge-Zonen),
|
||||||
|
werden mit scope: "note" angelegt. Callouts, die bereits in Chunks erfasst wurden,
|
||||||
|
werden übersprungen, um Duplikate zu vermeiden.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
markdown_body: Original-Markdown-Text (vor Chunking-Filterung)
|
||||||
|
note_id: ID der Note
|
||||||
|
existing_chunk_callouts: Set von (kind, target, section) Tupeln aus Chunks
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[dict]: Liste von Edge-Payloads mit scope: "note"
|
||||||
|
"""
|
||||||
|
if not markdown_body:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if existing_chunk_callouts is None:
|
||||||
|
existing_chunk_callouts = set()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# WP-24c v4.2.1: Prüfe, ob dieser Callout bereits in einem Chunk vorkommt
|
||||||
|
callout_key = (k, t, sec)
|
||||||
|
if callout_key in existing_chunk_callouts:
|
||||||
|
# Callout ist bereits in Chunk erfasst -> überspringe (wird mit chunk-Scope angelegt)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# WP-24c v4.2.1: Callout ist NICHT in Chunks -> lege mit scope: "note" an
|
||||||
|
# (typischerweise in Edge-Zonen, die nicht gechunkt werden)
|
||||||
|
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,
|
||||||
|
extra=payload
|
||||||
|
))
|
||||||
|
|
||||||
return edges
|
return edges
|
||||||
|
|
||||||
|
|
@ -151,7 +216,10 @@ def build_edges_for_note(
|
||||||
|
|
||||||
# WP-24c v4.2.0: Note-Scope Zonen Extraktion (VOR Chunk-Verarbeitung)
|
# WP-24c v4.2.0: Note-Scope Zonen Extraktion (VOR Chunk-Verarbeitung)
|
||||||
note_scope_edges: List[dict] = []
|
note_scope_edges: List[dict] = []
|
||||||
|
|
||||||
if markdown_body:
|
if markdown_body:
|
||||||
|
# 1. Note-Scope Zonen (Wikilinks und Typed Relations)
|
||||||
|
# WP-24c v4.2.1: Callouts werden NICHT hier extrahiert, da sie separat behandelt werden
|
||||||
zone_links = extract_note_scope_zones(markdown_body)
|
zone_links = extract_note_scope_zones(markdown_body)
|
||||||
for kind, raw_target in zone_links:
|
for kind, raw_target in zone_links:
|
||||||
target, sec = parse_link_target(raw_target, note_id)
|
target, sec = parse_link_target(raw_target, note_id)
|
||||||
|
|
@ -213,6 +281,9 @@ def build_edges_for_note(
|
||||||
reg = load_types_registry()
|
reg = load_types_registry()
|
||||||
defaults = get_edge_defaults_for(note_type, reg)
|
defaults = get_edge_defaults_for(note_type, reg)
|
||||||
refs_all: List[str] = []
|
refs_all: List[str] = []
|
||||||
|
|
||||||
|
# WP-24c v4.2.1: Sammle alle Callout-Keys aus Chunks für Smart Logic
|
||||||
|
all_chunk_callout_keys: Set[Tuple[str, str, Optional[str]]] = set()
|
||||||
|
|
||||||
for ch in chunks:
|
for ch in chunks:
|
||||||
cid = _get(ch, "chunk_id", "id")
|
cid = _get(ch, "chunk_id", "id")
|
||||||
|
|
@ -249,12 +320,15 @@ def build_edges_for_note(
|
||||||
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))
|
||||||
|
|
||||||
# C. Callouts (> [!edge])
|
# C. Callouts (> [!edge]) - WP-24c v4.2.1: Sammle für Smart Logic
|
||||||
call_pairs, rem2 = extract_callout_relations(rem)
|
call_pairs, rem2 = extract_callout_relations(rem)
|
||||||
for k, raw_t in call_pairs:
|
for k, raw_t in call_pairs:
|
||||||
t, sec = parse_link_target(raw_t, note_id)
|
t, sec = parse_link_target(raw_t, note_id)
|
||||||
if not t: continue
|
if not t: continue
|
||||||
|
|
||||||
|
# WP-24c v4.2.1: Tracke Callout für spätere Deduplizierung (global sammeln)
|
||||||
|
all_chunk_callout_keys.add((k, t, sec))
|
||||||
|
|
||||||
# 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
|
||||||
payload = {
|
payload = {
|
||||||
"chunk_id": cid,
|
"chunk_id": cid,
|
||||||
|
|
@ -312,11 +386,23 @@ 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)
|
||||||
# Diese werden mit höherer Priorität behandelt, da sie explizite Note-Level Verbindungen sind
|
|
||||||
edges.extend(note_scope_edges)
|
edges.extend(note_scope_edges)
|
||||||
|
|
||||||
|
# 5) WP-24c v4.2.1: Callout-Extraktion aus Markdown (NACH Chunk-Verarbeitung)
|
||||||
|
# Smart Logic: Nur Callouts, die NICHT in Chunks vorkommen, werden mit scope: "note" angelegt
|
||||||
|
callout_edges_from_markdown: List[dict] = []
|
||||||
|
if markdown_body:
|
||||||
|
callout_edges_from_markdown = extract_callouts_from_markdown(
|
||||||
|
markdown_body,
|
||||||
|
note_id,
|
||||||
|
existing_chunk_callouts=all_chunk_callout_keys
|
||||||
|
)
|
||||||
|
edges.extend(callout_edges_from_markdown)
|
||||||
|
|
||||||
# 5) De-Duplizierung (In-Place) mit Priorisierung
|
# 6) De-Duplizierung (In-Place) mit Priorisierung
|
||||||
# WP-24c v4.2.0: Note-Scope Links haben Vorrang bei Duplikaten
|
# WP-24c v4.2.1: Smart Scope-Priorisierung
|
||||||
|
# - chunk-Scope wird bevorzugt (präzisere Information für RAG)
|
||||||
|
# - note-Scope gewinnt nur bei höherer Provenance-Priorität (z.B. explicit:note_zone)
|
||||||
# WP-24c v4.1.0: Da die EDGE-ID nun auf 5 Parametern basiert (inkl. target_section),
|
# WP-24c v4.1.0: Da die EDGE-ID nun auf 5 Parametern basiert (inkl. target_section),
|
||||||
# bleiben Links auf unterschiedliche Abschnitte derselben Note als eigenständige
|
# bleiben Links auf unterschiedliche Abschnitte derselben Note als eigenständige
|
||||||
# Kanten erhalten. Nur identische Sektions-Links werden nach Confidence und Provenance konsolidiert.
|
# Kanten erhalten. Nur identische Sektions-Links werden nach Confidence und Provenance konsolidiert.
|
||||||
|
|
@ -324,18 +410,17 @@ def build_edges_for_note(
|
||||||
for e in edges:
|
for e in edges:
|
||||||
eid = e["edge_id"]
|
eid = e["edge_id"]
|
||||||
|
|
||||||
# WP-24c v4.2.0: Priorisierung bei Duplikaten
|
|
||||||
# 1. Note-Scope Links (explicit:note_zone) haben höchste Priorität
|
|
||||||
# 2. Dann Confidence
|
|
||||||
# 3. Dann Provenance-Priority
|
|
||||||
if eid not in unique_map:
|
if eid not in unique_map:
|
||||||
unique_map[eid] = e
|
unique_map[eid] = e
|
||||||
else:
|
else:
|
||||||
existing = unique_map[eid]
|
existing = unique_map[eid]
|
||||||
|
existing_scope = existing.get("scope", "chunk")
|
||||||
|
new_scope = e.get("scope", "chunk")
|
||||||
existing_prov = existing.get("provenance", "")
|
existing_prov = existing.get("provenance", "")
|
||||||
new_prov = e.get("provenance", "")
|
new_prov = e.get("provenance", "")
|
||||||
|
|
||||||
# Note-Scope Zone Links haben Vorrang
|
# WP-24c v4.2.1: Scope-Priorisierung
|
||||||
|
# 1. explicit:note_zone hat höchste Priorität (unabhängig von Scope)
|
||||||
is_existing_note_zone = existing_prov == "explicit:note_zone"
|
is_existing_note_zone = existing_prov == "explicit:note_zone"
|
||||||
is_new_note_zone = new_prov == "explicit:note_zone"
|
is_new_note_zone = new_prov == "explicit:note_zone"
|
||||||
|
|
||||||
|
|
@ -346,10 +431,27 @@ def build_edges_for_note(
|
||||||
# Bestehender Link ist Note-Scope Zone -> behalte
|
# Bestehender Link ist Note-Scope Zone -> behalte
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# Beide sind Note-Scope oder beide nicht -> vergleiche Confidence
|
# 2. chunk-Scope bevorzugen (präzisere Information)
|
||||||
existing_conf = existing.get("confidence", 0)
|
if existing_scope == "chunk" and new_scope == "note":
|
||||||
new_conf = e.get("confidence", 0)
|
# Bestehender chunk-Scope -> behalte
|
||||||
if new_conf > existing_conf:
|
pass
|
||||||
|
elif existing_scope == "note" and new_scope == "chunk":
|
||||||
|
# Neuer chunk-Scope -> ersetze (präziser)
|
||||||
unique_map[eid] = e
|
unique_map[eid] = e
|
||||||
|
else:
|
||||||
|
# Gleicher Scope -> vergleiche Confidence und Provenance-Priority
|
||||||
|
existing_conf = existing.get("confidence", 0)
|
||||||
|
new_conf = e.get("confidence", 0)
|
||||||
|
|
||||||
|
# Provenance-Priority berücksichtigen
|
||||||
|
existing_priority = PROVENANCE_PRIORITY.get(existing_prov, 0.7)
|
||||||
|
new_priority = PROVENANCE_PRIORITY.get(new_prov, 0.7)
|
||||||
|
|
||||||
|
# Kombinierter Score: Confidence * Priority
|
||||||
|
existing_score = existing_conf * existing_priority
|
||||||
|
new_score = new_conf * new_priority
|
||||||
|
|
||||||
|
if new_score > existing_score:
|
||||||
|
unique_map[eid] = e
|
||||||
|
|
||||||
return list(unique_map.values())
|
return list(unique_map.values())
|
||||||
265
docs/03_Technical_References/AUDIT_CLEAN_CONTEXT_V4.2.0.md
Normal file
265
docs/03_Technical_References/AUDIT_CLEAN_CONTEXT_V4.2.0.md
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
# 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
|
||||||
Loading…
Reference in New Issue
Block a user