mindnet/app/services/semantic_analyzer.py

106 lines
3.8 KiB
Python
Raw Normal View History

2025-12-11 23:21:24 +01:00
"""
app/services/semantic_analyzer.py — Edge Validation & Filtering
Version: 1.1 (Robust JSON Parsing)
2025-12-11 23:21:24 +01:00
"""
2025-12-12 10:25:01 +01:00
2025-12-11 23:21:24 +01:00
import json
import logging
2025-12-12 11:40:38 +01:00
from typing import List, Optional
2025-12-12 10:25:01 +01:00
from dataclasses import dataclass
2025-12-11 23:21:24 +01:00
2025-12-12 11:40:38 +01:00
# Importe
2025-12-11 23:21:24 +01:00
from app.services.llm_service import LLMService
logger = logging.getLogger(__name__)
class SemanticAnalyzer:
def __init__(self):
self.llm = LLMService()
2025-12-12 11:40:38 +01:00
async def assign_edges_to_chunk(self, chunk_text: str, all_edges: List[str], note_type: str) -> List[str]:
2025-12-11 23:21:24 +01:00
"""
2025-12-12 11:40:38 +01:00
Sendet einen Chunk und eine Liste potenzieller Kanten an das LLM.
Das LLM filtert heraus, welche Kanten für diesen Chunk relevant sind.
2025-12-11 23:21:24 +01:00
"""
2025-12-12 11:40:38 +01:00
if not all_edges:
return []
2025-12-12 10:39:40 +01:00
2025-12-12 11:40:38 +01:00
# 1. Prompt laden
prompt_template = self.llm.prompts.get("edge_allocation_template")
# Fallback, falls Prompt nicht in YAML definiert ist (für Tests ohne volle Config)
2025-12-12 11:40:38 +01:00
if not prompt_template:
prompt_template = (
"TASK: Wähle aus den Kandidaten die relevanten Kanten für den Text.\n"
"TEXT: {chunk_text}\n"
"KANDIDATEN: {edge_list}\n"
"OUTPUT: JSON Liste von Strings [\"kind:target\"]."
)
2025-12-12 11:40:38 +01:00
# 2. Kandidaten-Liste formatieren
edges_str = "\n".join([f"- {e}" for e in all_edges])
# 3. Prompt füllen
final_prompt = prompt_template.format(
chunk_text=chunk_text[:3000],
2025-12-12 11:40:38 +01:00
edge_list=edges_str
2025-12-11 23:21:24 +01:00
)
try:
2025-12-12 11:40:38 +01:00
# 4. LLM Call mit JSON Erzwingung
2025-12-12 09:35:21 +01:00
response_json = await self.llm.generate_raw_response(
2025-12-12 11:40:38 +01:00
prompt=final_prompt,
2025-12-12 09:35:21 +01:00
force_json=True
)
2025-12-12 10:25:01 +01:00
# 5. Parsing & Cleaning
2025-12-12 11:40:38 +01:00
clean_json = response_json.replace("```json", "").replace("```", "").strip()
if not clean_json: return []
2025-12-11 23:21:24 +01:00
2025-12-12 11:40:38 +01:00
data = json.loads(clean_json)
valid_edges = []
2025-12-12 11:40:38 +01:00
# 6. Robuste Validierung (List vs Dict)
2025-12-12 11:40:38 +01:00
if isinstance(data, list):
# Standardfall: ["kind:target", ...]
2025-12-12 11:40:38 +01:00
valid_edges = [str(e) for e in data if isinstance(e, str) and ":" in e]
2025-12-12 11:40:38 +01:00
elif isinstance(data, dict):
# Abweichende Formate behandeln
2025-12-12 11:40:38 +01:00
for key, val in data.items():
# Fall A: {"edges": ["kind:target"]}
if key.lower() in ["edges", "results", "kanten"] and isinstance(val, list):
valid_edges.extend([str(e) for e in val if isinstance(e, str) and ":" in e])
# Fall B: {"kind": "target"} (Das beobachtete Format im Log)
elif isinstance(val, str):
# Wir rekonstruieren "kind:target"
valid_edges.append(f"{key}:{val}")
# Fall C: {"kind": ["target1", "target2"]}
elif isinstance(val, list):
for target in val:
if isinstance(target, str):
valid_edges.append(f"{key}:{target}")
# Safety: Filtere nur Kanten, die halbwegs valide aussehen
return [e for e in valid_edges if ":" in e]
2025-12-12 11:40:38 +01:00
2025-12-12 10:25:01 +01:00
except json.JSONDecodeError:
logger.warning("SemanticAnalyzer: LLM lieferte kein valides JSON. Ignoriere Zuweisung.")
2025-12-12 11:40:38 +01:00
return []
2025-12-11 23:21:24 +01:00
except Exception as e:
2025-12-12 11:40:38 +01:00
logger.error(f"SemanticAnalyzer Error: {e}")
return []
2025-12-11 23:21:24 +01:00
async def close(self):
if self.llm:
2025-12-12 10:25:01 +01:00
await self.llm.close()
2025-12-12 11:40:38 +01:00
# Singleton Helper
2025-12-12 10:25:01 +01:00
_analyzer_instance = None
def get_semantic_analyzer():
global _analyzer_instance
if _analyzer_instance is None:
_analyzer_instance = SemanticAnalyzer()
return _analyzer_instance