StudyAgent / agent.py
smlflg's picture
Initial public upload from Projekte/StudyAgent
c72a1e8 verified
Raw History Blame Contribute Delete
4.83 kB
import json
import os
from datetime import datetime
import httpx
MINIMAX_API_KEY = os.environ.get("MINIMAX_API_KEY", "")
MINIMAX_MODEL = "MiniMax-M2.5"
MINIMAX_ENDPOINT = "https://api.minimaxi.chat/v1/text/chatcompletion_v2"
SYSTEM_PROMPT = """Du bist ein ADHS-freundlicher Study Buddy. Deine Aufgabe: Zerlege Lernaufgaben in kleine, machbare 5-Minuten-Bloecke.
Regeln:
- Jeder Block hat einen eigenen Titel der sich wie ein FRISCHER ANFANG anfuehlt (z.B. "Oeffne die Seite und lies den ersten Absatz")
- NICHT: "Weiterlesen", "Aufgabe 3 von 10" — das toetet die Motivation
- Jeder Block hat einen kurzen Kontext-Satz: was genau tun, wo anfangen
- Buddy-Ton: supportiv, keine Vorwuerfe, kein Druck
- Im Panik-Modus (<48h): nur 3-5 Steps, nur das Absolut-Wichtigste, direkter Ton
Antworte NUR als JSON array (kein Markdown, kein Text drumrum):
[{"title": "...", "context": "...", "duration_minutes": 5}]"""
def _dummy_steps(title: str, panic: bool) -> list[dict]:
"""Fallback steps when no API key is configured."""
if panic:
return [
{
"title": f"Oeffne deine Unterlagen zu '{title}'",
"context": "Lies nur die Ueberschriften und fett markierten Begriffe. Nicht mehr.",
"duration_minutes": 5,
},
{
"title": "Die wichtigsten 3 Punkte notieren",
"context": "Schreib auf ein leeres Blatt: Was muss ich auf jeden Fall wissen?",
"duration_minutes": 5,
},
{
"title": "Einen Durchgang — fertig",
"context": "Lies deine 3 Punkte laut vor. Das war's. Du bist bereit.",
"duration_minutes": 5,
},
]
return [
{
"title": f"Starte mit '{title}' — nur die erste Seite",
"context": "Oeffne das Material und lies genau eine Seite. Kein Druck weiterzumachen.",
"duration_minutes": 5,
},
{
"title": "Notiere eine Frage die du hast",
"context": "Was ist noch unklar? Schreib es auf. Das hilft deinem Gehirn sich zu orientieren.",
"duration_minutes": 5,
},
{
"title": "Noch eine Seite — du bist schon drin",
"context": "Du hast den schwierigsten Teil hinter dir (das Anfangen). Lies weiter.",
"duration_minutes": 5,
},
{
"title": "Kurze Zusammenfassung in eigenen Worten",
"context": "Was hast du gerade gelesen? Ein Satz reicht. Kein perfekter Satz noetig.",
"duration_minutes": 5,
},
]
async def generate_micro_steps(
deadline_title: str,
deadline_description: str,
due_date: datetime,
hours_until_due: float,
) -> list[dict]:
panic = hours_until_due < 48
if not MINIMAX_API_KEY:
return _dummy_steps(deadline_title, panic)
if hours_until_due < 0:
mode_hint = "PANIK-MODUS: Deadline ist ueberschritten! Sofortmassnahmen. Maximal 4 Steps, nur das Absolut-Wichtigste."
elif panic:
mode_hint = f"PANIK-MODUS: Nur {int(hours_until_due)}h bis zur Deadline. Maximal 4 Steps, nur das Wichtigste."
else:
mode_hint = f"Normale Vorbereitung. Deadline in {int(hours_until_due)}h. Generiere 4-6 Steps."
user_message = (
f"Aufgabe: {deadline_title}\n"
f"Details: {deadline_description or 'keine weiteren Details'}\n"
f"Faellig: {due_date.strftime('%d.%m.%Y %H:%M')}\n"
f"{mode_hint}"
)
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
MINIMAX_ENDPOINT,
headers={
"Authorization": f"Bearer {MINIMAX_API_KEY}",
"Content-Type": "application/json",
},
json={
"model": MINIMAX_MODEL,
"max_tokens": 1024,
"temperature": 0.3,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
},
)
response.raise_for_status()
data = response.json()
msg = data["choices"][0]["message"]
raw = (msg.get("content") or "").strip()
if not raw:
# M2.5 reasoning fallback
reasoning = (msg.get("reasoning_content") or "").strip()
if reasoning:
raw = reasoning
# Strip markdown code fences if present
if raw.startswith("```"):
raw = raw.split("```")[1]
if raw.startswith("json"):
raw = raw[4:]
raw = raw.strip()
steps = json.loads(raw)
# Ensure required fields
for step in steps:
step.setdefault("duration_minutes", 5)
return steps