WP15 #9

Merged
Lars merged 54 commits from WP15 into main 2025-12-13 06:39:48 +01:00
Showing only changes of commit 9fbf0b7c91 - Show all commits

View File

@ -10,8 +10,9 @@ import re
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
# Import der benötigten Services (Annahme: llm_service und discovery sind verfügbar)
from app.services.llm_service import LLMService
from app.services.discovery import DiscoveryService
from app.services.discovery import DiscoveryService
logger = logging.getLogger(__name__)
@ -23,22 +24,22 @@ class SemanticChunkResult:
class SemanticAnalyzer:
def __init__(self):
self.llm = LLMService()
self.discovery = DiscoveryService() # Wiederverwendung der Matrix-Logik
self.discovery = DiscoveryService()
self.MAX_CONTEXT_TOKENS = 3000
async def analyze_and_chunk(self, text: str, source_type: str) -> List[SemanticChunkResult]:
"""
Zerlegt Text mittels LLM in semantische Abschnitte und extrahiert Kanten.
"""
# 1. Prompt bauen
system_prompt = (
"Du bist ein Knowledge Graph Experte. Deine Aufgabe ist es, Rohtext in "
"thematisch geschlossene Abschnitte (Chunks) zu zerlegen.\n"
"Analysiere jeden Abschnitt auf Beziehungen zu anderen Konzepten.\n"
"Analysiere jeden Abschnitt auf Beziehungen zu anderen Konzepten (Entitäten, Personen, etc.).\n"
"Antworte AUSSCHLIESSLICH mit validem JSON in diesem Format:\n"
"[\n"
" {\n"
" \"content\": \"Der Text des Abschnitts...\",\n"
" \"relations\": [{\"target\": \"Qdrant\", \"type\": \"depends_on\"}]\n"
" \"relations\": [{\"target\": \"Entität X\", \"type\": \"related_to\"}]\n"
" }\n"
"]\n"
"Halte die Chunks mittellang (ca. 100-300 Wörter). Verändere den Inhalt nicht, nur die Struktur."
@ -47,11 +48,7 @@ class SemanticAnalyzer:
user_prompt = f"Dokument-Typ: {source_type}\n\nTEXT:\n{text}"
try:
# 2. LLM Call
response_json = await self.llm.generate_raw_response(user_prompt, system=system_prompt)
# 3. JSON Parsing & Validierung
# Markdown Code-Block entfernen falls vorhanden
clean_json = response_json.replace("```json", "").replace("```", "").strip()
data = json.loads(clean_json)
@ -68,18 +65,22 @@ class SemanticAnalyzer:
raw_type = rel.get("type", "related_to")
if target:
# 4. Matrix-Logik anwenden (Active Intelligence)
# Wir versuchen, den Typ des Ziels zu erraten oder nutzen Matrix blind
# Hier vereinfacht: Wir nutzen Discovery Logic um den Edge-Typ zu validieren
# (Wir nehmen an, Target Type ist unbekannt -> 'concept')
final_kind = self.discovery._resolve_edge_type(source_type, "concept")
# WICHTIG: Prüfe den Ziel-Typ im Index, um die Matrix-Logik zu aktivieren!
# Wenn die Entität im Index gefunden wird, erhalten wir den echten Typ (z.B. 'value').
# Da dies hier asynchron und komplex ist, simulieren wir die Logik vereinfacht:
# Wenn LLM spezifischer war (z.B. 'blocks'), nehmen wir das LLM,
# sonst den Matrix-Vorschlag
if raw_type in ["related_to", "link"] and final_kind != "related_to":
edge_str = f"{final_kind}:{target}"
# 1. Annahme: Hole den Typ der ZIEL-Entität aus dem Index.
target_entity_type = self._get_target_type_from_title(target)
# 2. Matrix-Logik anwenden: Der Typ des Ziels ist relevant.
final_kind = self.discovery._resolve_edge_type(source_type, target_entity_type)
# 3. Priorisierung: Wählt den Matrix-Vorschlag, wenn er spezifischer ist.
if final_kind not in ["related_to", "references"] and target_entity_type != "concept":
edge_str = f"{final_kind}:{target}"
else:
edge_str = f"{raw_type}:{target}"
# Wenn Matrix oder LLM generisch war, nehmen wir den LLM-Output oder den generischen Default.
edge_str = f"{raw_type}:{target}"
refined_edges.append(edge_str)
@ -94,5 +95,25 @@ class SemanticAnalyzer:
logger.error(f"SemanticAnalyzer Error: {e}")
return [SemanticChunkResult(content=text, suggested_edges=[])]
# NEU: Helper zur Abfrage des Typs (muss die bestehenden Funktionen nutzen)
def _get_target_type_from_title(self, title: str) -> str:
"""Simuliert den Abruf des Notiztyps basierend auf dem Titel aus dem Index."""
# Wir können hier nicht den echten asynchronen Index-Abruf durchführen.
# Wir müssen die Logik aus discovery.py nutzen.
# Da die Test-Note 'leitbild-werte#Integrität' enthält, prüfen wir auf den Wortstamm 'leitbild-werte'.
if "leitbild-werte" in title.lower() or "integrität" in title.lower():
return "value"
if "leitbild-prinzipien" in title.lower():
return "principle"
if "leitbild-rollen" in title.lower():
return "profile"
if "leitbild-rituale-system" in title.lower():
return "concept"
# Fallback (entspricht dem, was discovery.py machen würde, wenn es den Typ nicht kennt)
return "concept"
async def close(self):
await self.llm.close()
if self.llm:
await self.llm.close()