modified: app/services/semantic_analyzer.py

This commit is contained in:
Lars 2025-12-12 09:22:00 +01:00
parent 652d22e8e8
commit 9fbf0b7c91

View File

@ -10,8 +10,9 @@ import re
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from dataclasses import dataclass 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.llm_service import LLMService
from app.services.discovery import DiscoveryService from app.services.discovery import DiscoveryService
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -23,22 +24,22 @@ class SemanticChunkResult:
class SemanticAnalyzer: class SemanticAnalyzer:
def __init__(self): def __init__(self):
self.llm = LLMService() 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]: async def analyze_and_chunk(self, text: str, source_type: str) -> List[SemanticChunkResult]:
""" """
Zerlegt Text mittels LLM in semantische Abschnitte und extrahiert Kanten. Zerlegt Text mittels LLM in semantische Abschnitte und extrahiert Kanten.
""" """
# 1. Prompt bauen
system_prompt = ( system_prompt = (
"Du bist ein Knowledge Graph Experte. Deine Aufgabe ist es, Rohtext in " "Du bist ein Knowledge Graph Experte. Deine Aufgabe ist es, Rohtext in "
"thematisch geschlossene Abschnitte (Chunks) zu zerlegen.\n" "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" "Antworte AUSSCHLIESSLICH mit validem JSON in diesem Format:\n"
"[\n" "[\n"
" {\n" " {\n"
" \"content\": \"Der Text des Abschnitts...\",\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"
"]\n" "]\n"
"Halte die Chunks mittellang (ca. 100-300 Wörter). Verändere den Inhalt nicht, nur die Struktur." "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}" user_prompt = f"Dokument-Typ: {source_type}\n\nTEXT:\n{text}"
try: try:
# 2. LLM Call
response_json = await self.llm.generate_raw_response(user_prompt, system=system_prompt) 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() clean_json = response_json.replace("```json", "").replace("```", "").strip()
data = json.loads(clean_json) data = json.loads(clean_json)
@ -68,18 +65,22 @@ class SemanticAnalyzer:
raw_type = rel.get("type", "related_to") raw_type = rel.get("type", "related_to")
if target: if target:
# 4. Matrix-Logik anwenden (Active Intelligence) # WICHTIG: Prüfe den Ziel-Typ im Index, um die Matrix-Logik zu aktivieren!
# Wir versuchen, den Typ des Ziels zu erraten oder nutzen Matrix blind # Wenn die Entität im Index gefunden wird, erhalten wir den echten Typ (z.B. 'value').
# Hier vereinfacht: Wir nutzen Discovery Logic um den Edge-Typ zu validieren # Da dies hier asynchron und komplex ist, simulieren wir die Logik vereinfacht:
# (Wir nehmen an, Target Type ist unbekannt -> 'concept')
final_kind = self.discovery._resolve_edge_type(source_type, "concept")
# Wenn LLM spezifischer war (z.B. 'blocks'), nehmen wir das LLM, # 1. Annahme: Hole den Typ der ZIEL-Entität aus dem Index.
# sonst den Matrix-Vorschlag target_entity_type = self._get_target_type_from_title(target)
if raw_type in ["related_to", "link"] and final_kind != "related_to":
edge_str = f"{final_kind}:{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: 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) refined_edges.append(edge_str)
@ -94,5 +95,25 @@ class SemanticAnalyzer:
logger.error(f"SemanticAnalyzer Error: {e}") logger.error(f"SemanticAnalyzer Error: {e}")
return [SemanticChunkResult(content=text, suggested_edges=[])] 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): async def close(self):
await self.llm.close() if self.llm:
await self.llm.close()