This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/ai-content-generator/app/services/claude_service.py
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

365 lines
9.8 KiB
Python

"""
Claude Service
Integration mit Claude API für Content-Generierung
"""
import os
from typing import List, Dict, Any, Optional
from anthropic import Anthropic
class ClaudeService:
"""Claude API Service"""
def __init__(self):
self.api_key = os.getenv("ANTHROPIC_API_KEY")
self.client = Anthropic(api_key=self.api_key) if self.api_key else None
self.model = "claude-sonnet-4-5-20251101" # Latest model
def is_configured(self) -> bool:
"""Check if API key is configured"""
return self.client is not None
async def generate_content(
self,
prompt: str,
system_prompt: Optional[str] = None,
max_tokens: int = 4000,
temperature: float = 1.0
) -> str:
"""
Generate content with Claude
Args:
prompt: User prompt
system_prompt: System prompt (optional)
max_tokens: Maximum tokens to generate
temperature: Sampling temperature
Returns:
Generated text
"""
if not self.client:
raise ValueError("Claude API not configured. Set ANTHROPIC_API_KEY environment variable.")
messages = [{"role": "user", "content": prompt}]
kwargs = {
"model": self.model,
"max_tokens": max_tokens,
"temperature": temperature,
"messages": messages
}
if system_prompt:
kwargs["system"] = system_prompt
response = self.client.messages.create(**kwargs)
return response.content[0].text
async def generate_quiz_questions(
self,
topic: str,
materials: List[Dict[str, Any]],
target_grade: str,
num_questions: int = 10
) -> List[Dict[str, Any]]:
"""Generate Quiz questions"""
material_text = self._format_materials(materials)
prompt = f"""Erstelle {num_questions} Multiple-Choice-Fragen zum Thema "{topic}" für Klassenstufe {target_grade}.
Materialien:
{material_text}
Erstelle Fragen die:
1. Das Verständnis testen
2. Auf den Materialien basieren
3. Altersgerecht sind
4. 4 Antwortmöglichkeiten haben (1 richtig, 3 falsch)
Formatiere die Ausgabe als JSON-Array:
[
{{
"question": "Frage text?",
"options": ["Option A", "Option B", "Option C", "Option D"],
"correct_answer": 0,
"explanation": "Erklärung warum die Antwort richtig ist"
}}
]
Nur das JSON-Array zurückgeben, keine zusätzlichen Texte."""
response = await self.generate_content(
prompt=prompt,
system_prompt="Du bist ein pädagogischer Experte der Quizfragen erstellt."
)
# Parse JSON
import json
try:
questions = json.loads(response)
return questions
except json.JSONDecodeError:
# Try to extract JSON from response
import re
json_match = re.search(r'\[.*\]', response, re.DOTALL)
if json_match:
questions = json.loads(json_match.group())
return questions
raise ValueError("Could not parse quiz questions from Claude response")
async def generate_flashcards(
self,
topic: str,
materials: List[Dict[str, Any]],
target_grade: str,
num_cards: int = 15
) -> List[Dict[str, str]]:
"""Generate Flashcards"""
material_text = self._format_materials(materials)
prompt = f"""Erstelle {num_cards} Lernkarten (Flashcards) zum Thema "{topic}" für Klassenstufe {target_grade}.
Materialien:
{material_text}
Erstelle Karten die:
1. Wichtige Begriffe und Konzepte abdecken
2. Kurz und prägnant sind
3. Zum Wiederholen geeignet sind
Formatiere die Ausgabe als JSON-Array:
[
{{
"front": "Begriff oder Frage",
"back": "Definition oder Antwort"
}}
]
Nur das JSON-Array zurückgeben."""
response = await self.generate_content(
prompt=prompt,
system_prompt="Du bist ein Experte für Lernkarten-Design."
)
import json
import re
json_match = re.search(r'\[.*\]', response, re.DOTALL)
if json_match:
return json.loads(json_match.group())
return json.loads(response)
async def generate_fill_blanks_text(
self,
topic: str,
materials: List[Dict[str, Any]],
target_grade: str
) -> Dict[str, Any]:
"""Generate Fill-in-the-Blanks exercise"""
material_text = self._format_materials(materials)
prompt = f"""Erstelle einen Lückentext zum Thema "{topic}" für Klassenstufe {target_grade}.
Materialien:
{material_text}
Erstelle einen Text mit 10-15 Lücken. Markiere Lücken mit *Wort*.
Formatiere als JSON:
{{
"title": "Titel des Lückentexts",
"text": "Der Text mit *Lücken* markiert...",
"hints": "Hilfreiche Hinweise"
}}
Nur JSON zurückgeben."""
response = await self.generate_content(
prompt=prompt,
system_prompt="Du bist ein Experte für Lückentexte."
)
import json
import re
json_match = re.search(r'\{.*\}', response, re.DOTALL)
if json_match:
return json.loads(json_match.group())
return json.loads(response)
async def generate_drag_drop_exercise(
self,
topic: str,
materials: List[Dict[str, Any]],
target_grade: str
) -> Dict[str, Any]:
"""Generate Drag-and-Drop exercise"""
material_text = self._format_materials(materials)
prompt = f"""Erstelle eine Drag-and-Drop Zuordnungsaufgabe zum Thema "{topic}" für Klassenstufe {target_grade}.
Materialien:
{material_text}
Erstelle 3-4 Kategorien (Zonen) und 8-12 Elemente zum Zuordnen.
Formatiere als JSON:
{{
"title": "Titel der Aufgabe",
"question": "Aufgabenstellung",
"zones": [
{{ "id": 1, "name": "Kategorie 1" }},
{{ "id": 2, "name": "Kategorie 2" }}
],
"draggables": [
{{ "id": 1, "text": "Element 1", "correctZoneId": 1 }},
{{ "id": 2, "text": "Element 2", "correctZoneId": 2 }}
]
}}
Nur JSON zurückgeben."""
response = await self.generate_content(
prompt=prompt,
system_prompt="Du bist ein Experte für interaktive Lernaufgaben."
)
import json
import re
json_match = re.search(r'\{.*\}', response, re.DOTALL)
if json_match:
return json.loads(json_match.group())
return json.loads(response)
async def generate_memory_pairs(
self,
topic: str,
materials: List[Dict[str, Any]],
target_grade: str,
num_pairs: int = 8
) -> List[Dict[str, str]]:
"""Generate Memory Game pairs"""
material_text = self._format_materials(materials)
prompt = f"""Erstelle {num_pairs} Memory-Paare zum Thema "{topic}" für Klassenstufe {target_grade}.
Materialien:
{material_text}
Jedes Paar besteht aus zwei zusammengehörigen Begriffen/Konzepten.
Formatiere als JSON-Array:
[
{{ "card1": "Begriff 1", "card2": "Zugehöriger Begriff" }}
]
Nur JSON zurückgeben."""
response = await self.generate_content(
prompt=prompt,
system_prompt="Du bist ein Experte für Memory-Spiele."
)
import json
import re
json_match = re.search(r'\[.*\]', response, re.DOTALL)
if json_match:
return json.loads(json_match.group())
return json.loads(response)
async def generate_timeline_events(
self,
topic: str,
materials: List[Dict[str, Any]],
target_grade: str
) -> List[Dict[str, Any]]:
"""Generate Timeline events"""
material_text = self._format_materials(materials)
prompt = f"""Erstelle eine Timeline mit 5-8 Ereignissen zum Thema "{topic}" für Klassenstufe {target_grade}.
Materialien:
{material_text}
Formatiere als JSON-Array:
[
{{
"year": "Jahr oder Zeitpunkt",
"title": "Ereignis Titel",
"description": "Kurze Beschreibung"
}}
]
Nur JSON zurückgeben."""
response = await self.generate_content(
prompt=prompt,
system_prompt="Du bist ein Experte für chronologische Darstellungen."
)
import json
import re
json_match = re.search(r'\[.*\]', response, re.DOTALL)
if json_match:
return json.loads(json_match.group())
return json.loads(response)
async def generate_presentation_slides(
self,
topic: str,
materials: List[Dict[str, Any]],
target_grade: str,
num_slides: int = 5
) -> List[Dict[str, str]]:
"""Generate Presentation slides"""
material_text = self._format_materials(materials)
prompt = f"""Erstelle {num_slides} Präsentationsfolien zum Thema "{topic}" für Klassenstufe {target_grade}.
Materialien:
{material_text}
Formatiere als JSON-Array:
[
{{
"title": "Folien Titel",
"content": "Folien Inhalt (2-4 Sätze)",
"backgroundColor": "#ffffff"
}}
]
Nur JSON zurückgeben."""
response = await self.generate_content(
prompt=prompt,
system_prompt="Du bist ein Experte für Präsentationen."
)
import json
import re
json_match = re.search(r'\[.*\]', response, re.DOTALL)
if json_match:
return json.loads(json_match.group())
return json.loads(response)
def _format_materials(self, materials: List[Dict[str, Any]]) -> str:
"""Format materials for prompt"""
if not materials:
return "Keine Materialien vorhanden."
formatted = []
for i, material in enumerate(materials, 1):
formatted.append(f"Material {i} ({material.get('type', 'unknown')}):")
formatted.append(material.get('content', '')[:2000]) # Limit content
formatted.append("")
return "\n".join(formatted)