Files
breakpilot-lehrer/backend-lehrer/llm_gateway/routes/communication.py
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

404 lines
12 KiB
Python

"""
Communication API Routes.
API-Endpoints für KI-gestützte Lehrer-Eltern-Kommunikation.
Basiert auf den Prinzipien der gewaltfreien Kommunikation (GFK)
und deutschen Schulgesetzen.
"""
import logging
from typing import Optional, List
from datetime import datetime
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel, Field
from ..services.communication_service import (
get_communication_service,
CommunicationService,
CommunicationType,
CommunicationTone,
)
from ..services.inference import InferenceService, get_inference_service
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/communication", tags=["communication"])
# =============================================================================
# Pydantic Models
# =============================================================================
class CommunicationTypeResponse(BaseModel):
"""Response für Kommunikationstypen."""
value: str
label: str
class ToneResponse(BaseModel):
"""Response für Tonalitäten."""
value: str
label: str
class StateResponse(BaseModel):
"""Response für Bundesländer."""
value: str
label: str
class LegalReferenceResponse(BaseModel):
"""Response für rechtliche Referenzen."""
law: str
paragraph: str
title: str
summary: str
relevance: str
class GFKPrincipleResponse(BaseModel):
"""Response für GFK-Prinzipien."""
principle: str
description: str
example: str
class GenerateRequest(BaseModel):
"""Request für Nachrichtengenerierung."""
communication_type: str = Field(..., description="Art der Kommunikation (z.B. 'behavior', 'academic')")
tone: str = Field("professional", description="Tonalität (formal, professional, warm, concerned, appreciative)")
state: str = Field("NRW", description="Bundesland für rechtliche Referenzen")
student_name: str = Field(..., description="Name des Schülers/der Schülerin")
parent_name: str = Field(..., description="Name der Eltern (z.B. 'Frau Müller')")
situation: str = Field(..., description="Beschreibung der Situation")
additional_info: Optional[str] = Field(None, description="Zusätzliche Informationen")
class GenerateResponse(BaseModel):
"""Response für generierte Nachrichten."""
message: str
subject: str
validation: dict
legal_references: List[LegalReferenceResponse]
gfk_principles: List[GFKPrincipleResponse]
class ValidateRequest(BaseModel):
"""Request für Textvalidierung."""
text: str = Field(..., description="Der zu validierende Text")
class ValidateResponse(BaseModel):
"""Response für Validierung."""
is_valid: bool
issues: List[str]
suggestions: List[str]
positive_elements: List[str]
gfk_score: float
# =============================================================================
# Endpoints
# =============================================================================
@router.get("/types", response_model=List[CommunicationTypeResponse])
async def get_communication_types():
"""
Gibt alle verfügbaren Kommunikationstypen zurück.
Returns:
Liste aller Kommunikationstypen mit Wert und Label
"""
service = get_communication_service()
return service.get_all_communication_types()
@router.get("/tones", response_model=List[ToneResponse])
async def get_tones():
"""
Gibt alle verfügbaren Tonalitäten zurück.
Returns:
Liste aller Tonalitäten mit Wert und Label
"""
service = get_communication_service()
return service.get_all_tones()
@router.get("/states", response_model=List[StateResponse])
async def get_states():
"""
Gibt alle verfügbaren Bundesländer zurück.
Returns:
Liste aller Bundesländer mit Wert und Label
"""
service = get_communication_service()
return service.get_states()
@router.get("/legal-references/{state}")
async def get_legal_references(state: str):
"""
Gibt rechtliche Referenzen für ein Bundesland zurück.
Args:
state: Bundesland-Kürzel (z.B. NRW, BY)
Returns:
Rechtliche Referenzen für das Bundesland
"""
service = get_communication_service()
refs = service.get_legal_references(state, "elternpflichten")
return [
LegalReferenceResponse(
law=ref.law,
paragraph=ref.paragraph,
title=ref.title,
summary=ref.summary,
relevance=ref.relevance
)
for ref in refs
]
@router.get("/gfk-principles", response_model=List[GFKPrincipleResponse])
async def get_gfk_principles():
"""
Gibt die Prinzipien der gewaltfreien Kommunikation zurück.
Returns:
Liste der GFK-Prinzipien mit Beschreibung und Beispielen
"""
service = get_communication_service()
principles = service.get_gfk_guidance(CommunicationType.GENERAL_INFO)
return [
GFKPrincipleResponse(
principle=p.principle,
description=p.description,
example=p.example
)
for p in principles
]
@router.post("/generate", response_model=GenerateResponse)
async def generate_communication(request: GenerateRequest):
"""
Generiert einen Elternbrief basierend auf dem Kontext.
Args:
request: GenerateRequest mit allen nötigen Informationen
Returns:
GenerateResponse mit generiertem Text und Metadaten
"""
service = get_communication_service()
# Kommunikationstyp validieren
try:
comm_type = CommunicationType(request.communication_type)
except ValueError:
raise HTTPException(
status_code=400,
detail=f"Ungültiger Kommunikationstyp: {request.communication_type}"
)
# Tonalität validieren
try:
tone = CommunicationTone(request.tone)
except ValueError:
tone = CommunicationTone.PROFESSIONAL
# System- und User-Prompt erstellen
system_prompt = service.build_system_prompt(comm_type, request.state, tone)
user_prompt = service.build_user_prompt(comm_type, {
"student_name": request.student_name,
"parent_name": request.parent_name,
"situation": request.situation,
"additional_info": request.additional_info,
})
# Inference-Service aufrufen
try:
inference_service = get_inference_service()
response = await inference_service.generate(
prompt=user_prompt,
system_prompt=system_prompt,
temperature=0.7, # Etwas kreativ, aber kontrolliert
max_tokens=2000,
)
generated_message = response.get("content", "")
except Exception as e:
logger.error(f"Fehler bei der Nachrichtengenerierung: {e}")
# Fallback: Vorlage verwenden
template = service.get_template(comm_type)
generated_message = f"""{template['opening'].format(
parent_name=request.parent_name,
student_name=request.student_name,
topic=request.situation[:50] + '...' if len(request.situation) > 50 else request.situation
)}
{request.situation}
{template['closing'].format(
student_name=request.student_name,
legal_reference=f"des Schulgesetzes"
)}"""
# Validierung durchführen
validation = service.validate_communication(generated_message)
# Rechtliche Referenzen holen
topic_map = {
CommunicationType.ATTENDANCE: "schulpflicht",
CommunicationType.BEHAVIOR: "ordnungsmassnahmen",
CommunicationType.ACADEMIC: "foerderung",
CommunicationType.SPECIAL_NEEDS: "foerderung",
}
topic = topic_map.get(comm_type, "elternpflichten")
legal_refs = service.get_legal_references(request.state, topic)
# GFK-Prinzipien
gfk_principles = service.get_gfk_guidance(comm_type)
# Betreff generieren
template = service.get_template(comm_type)
subject = template.get("subject", "Mitteilung der Schule").format(
student_name=request.student_name,
topic=request.situation[:30] + '...' if len(request.situation) > 30 else request.situation
)
return GenerateResponse(
message=generated_message,
subject=subject,
validation=validation,
legal_references=[
LegalReferenceResponse(
law=ref.law,
paragraph=ref.paragraph,
title=ref.title,
summary=ref.summary,
relevance=ref.relevance
)
for ref in legal_refs
],
gfk_principles=[
GFKPrincipleResponse(
principle=p.principle,
description=p.description,
example=p.example
)
for p in gfk_principles
]
)
@router.post("/validate", response_model=ValidateResponse)
async def validate_communication(request: ValidateRequest):
"""
Validiert einen Text auf GFK-Konformität.
Args:
request: ValidateRequest mit dem zu prüfenden Text
Returns:
ValidateResponse mit Validierungsergebnissen
"""
service = get_communication_service()
result = service.validate_communication(request.text)
return ValidateResponse(
is_valid=result["is_valid"],
issues=result["issues"],
suggestions=result["suggestions"],
positive_elements=result["positive_elements"],
gfk_score=result["gfk_score"]
)
@router.post("/improve")
async def improve_communication(request: ValidateRequest):
"""
Verbessert einen bestehenden Text nach GFK-Prinzipien.
Args:
request: ValidateRequest mit dem zu verbessernden Text
Returns:
Verbesserter Text mit Änderungsvorschlägen
"""
service = get_communication_service()
# Erst validieren
validation = service.validate_communication(request.text)
if validation["is_valid"] and validation["gfk_score"] >= 0.8:
return {
"improved_text": request.text,
"changes": [],
"was_improved": False,
"message": "Der Text entspricht bereits den GFK-Prinzipien."
}
# System-Prompt für Verbesserung
system_prompt = """Du bist ein Experte für gewaltfreie Kommunikation (GFK) nach Marshall Rosenberg.
Deine Aufgabe ist es, einen Elternbrief zu verbessern, sodass er den GFK-Prinzipien entspricht.
VERBESSERUNGSREGELN:
1. Ersetze Bewertungen durch Beobachtungen
2. Ersetze "Sie müssen/sollten" durch Ich-Botschaften und Bitten
3. Entferne Schuldzuweisungen
4. Füge empathische Elemente hinzu
5. Behalte den sachlichen Inhalt bei
Gib den verbesserten Text zurück und erkläre kurz die wichtigsten Änderungen."""
user_prompt = f"""Bitte verbessere folgenden Elternbrief nach den GFK-Prinzipien:
---
{request.text}
---
Identifizierte Probleme:
{', '.join(validation['issues']) if validation['issues'] else 'Keine spezifischen Probleme gefunden, aber GFK-Score könnte verbessert werden.'}
Vorschläge:
{', '.join(validation['suggestions']) if validation['suggestions'] else 'Allgemeine Verbesserungen möglich.'}"""
try:
inference_service = get_inference_service()
response = await inference_service.generate(
prompt=user_prompt,
system_prompt=system_prompt,
temperature=0.5,
max_tokens=2500,
)
improved_text = response.get("content", request.text)
# Nochmal validieren
new_validation = service.validate_communication(improved_text)
return {
"improved_text": improved_text,
"original_issues": validation["issues"],
"was_improved": True,
"old_score": validation["gfk_score"],
"new_score": new_validation["gfk_score"],
"remaining_issues": new_validation["issues"],
}
except Exception as e:
logger.error(f"Fehler bei der Textverbesserung: {e}")
return {
"improved_text": request.text,
"changes": [],
"was_improved": False,
"error": str(e),
"message": "Die automatische Verbesserung ist derzeit nicht verfügbar."
}