Download agent.py from smlflg/StudyAgent: direct link, hf CLI and curl.
- Browser
- Download file 4.83 kB
-
https://huggingface.co/smlflg/StudyAgent/resolve/main/agent.py
- Command line
-
hf download hf://smlflg/StudyAgent/agent.py
-
curl -L -o agent.py https://huggingface.co/smlflg/StudyAgent/resolve/main/agent.py
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 | |