Ein datenschutzkonformes Intelligent Tutoring System für Schulen: Lernmaterial pro Stufe/Fach, ein individueller, inspizierbarer Lernstand pro Schüler:in, ein pädagogischer Agent, der erklärt und abfragt — und eine Lehrer:innen-Ansicht zur Kontrolle und Intervention. Gebaut für Minderjährige, deshalb Safety und Datenresidenz (CH/EU) als Voraussetzung, nicht als Zusatz.
Status: vollständig implementiert (Backend + Frontend), getestet (59+ Tests, CI-grün). Auth, das echte LLM und der Embedder sind bewusst noch Stubs — siehe Pre-Prod-Gates.
Schüler:in Lehrperson
↕ ↕
Pädagogischer Agent ⇄ Open Learner Model + Dashboard
(LangGraph-Loop) (Mastery + Unsicherheit)
↓ fragt Wissen ab
Retrieval-Router
┌────────┼────────┐
Semantic Individual Population
(geteilt) (1 Schüler) (Kohorte ≥ k)
└────────┼────────┘
╔════════════▼════════════╗
║ Safety & Isolation ║ Row-Level Security · Min-Cohort-Schwelle
╚════════════▼════════════╝
ein PostgreSQL + pgvector
- P1 — Safety in der DB: Isolation per Row-Level Security, nicht per App-
if. - P2 — Kuratierte Bewertung:
assessprüft gegen einen kuratierten Answer-Key (symbolisch viasympy), nicht per LLM. Generative Freiheit nur beim Erklären. - P3 — Das Modell verbessert sich, nicht der Agent: ein interpretierbares Bayesian Knowledge Tracing (BKT) pro Skill; Agentverhalten ist Funktion davon.
- P4 — PII verlässt die Maschine nicht: Anonymisierung vor jedem externen LLM-Call.
- P5 — Open Learner Model: Lehrer-Sicht zeigt Mastery inkl. Unsicherheit; Schüler-Sicht nie die Rohschätzung.
- P6 — Mensch im Loop: Lehrperson kann verifizieren, überschreiben, Notizen geben.
- P7 — Genau eine Plugin-Naht: fachspezifische Bewertung (
grading/). - P8 — Datenresidenz CH/EU · P9 —
uvausschliesslich (keinpip).
| Teil | Wahl |
|---|---|
| Datenbank (Vektor + relational + Aggregate) | ein PostgreSQL + pgvector |
| Isolation | Postgres Row-Level Security + Min-Cohort-Check |
| Agent / Retrieval-Router | LangGraph |
| Lernmodell | BKT (interpretierbar; DKT als späterer Swap) |
| Bewertung | GraderStrategy-Registry (Math symbolisch via sympy) |
| LLM / Embeddings | Backend umschaltbar (local Stub · frontier), anonymisiert |
| Backend | FastAPI + Pydantic (Python ≥ 3.12, uv) |
| Frontend | React + TypeScript + Vite + Tailwind (Design „EduSovereign") |
| Auth | Rollen student/teacher/admin (JWT-Stub; IdP folgt) |
apps/
api/ # FastAPI-Backend (uv)
src/its/
db/ # SQLAlchemy-Modelle, Session (RLS-Hook), Alembic-Migrationen
safety/ # rls.sql, scoping.py, cohort.py ← das Isolations-Gate
retrieval/ # router + semantic/individual/population + graph (CTE)
content/ # parser, ingest, kuratierte Items
learner_model/ # bkt.py, tracing.py, dkt.py (Stub)
grading/ # base + registry + math/language/history (Plugin-Naht)
agent/ # LangGraph: state.py, graph.py, nodes/ ← der Agent
llm/ # client.py, anonymize.py, embeddings.py, prompts/
api/ # student.py, teacher.py, content.py, dev.py, errors.py
web/ # React/TS-Frontend (Vite + Tailwind)
content/ # kuratierter Markdown-Vault (Demo)
infra/ # docker-compose (Postgres + pgvector)
scripts/ # seed.py, import_production.py, GitHub-Bootstrap
tests/ # pytest (inkl. CI-blockierende Safety-Tests)
Voraussetzungen: Docker,
uv, Node ≥ 20. Die Ports (DB 5433, API 8010, Web 5181) sind so gewählt, dass sie nicht mit anderen lokalen Diensten kollidieren — anpassbar ininfra/docker-compose.ymlundapps/web/vite.config.ts.
1) Datenbank
docker compose -f infra/docker-compose.yml up -d # Postgres + pgvector auf 127.0.0.1:54332) Backend (aus apps/api/)
# Umgebung (PowerShell-Beispiel)
$env:DATABASE_URL = "postgresql+psycopg://its:[email protected]:5433/its"
$env:DATA_MODE = "mock"; $env:AUTH_DEV_MODE = "1"
uv sync
uv run alembic upgrade head # Schema + RLS + Seed
uv run python ../../scripts/seed.py --profile demo # 1 Klasse, 25 Schüler:innen, 1 Lehrperson
uv run uvicorn its.main:app --host 127.0.0.1 --port 80103) Frontend (aus apps/web/)
npm install
npm run dev # → http://localhost:5181 (proxyt /student,/teacher,… an :8010)Im Browser oben rechts „Lehrer-Ansicht" bzw. in der Lehrer-Sidebar „Schüler-Ansicht" wechselt die Rolle. In der Lernsession eine Aufgabe beantworten → kuratierte Bewertung + Mastery-Update; im Lehrer-Dashboard auf „Details" → Open Learner Model mit Unsicherheit.
Der pädagogische Agent ist eine LangGraph-State-Machine unter apps/api/src/its/agent/:
route → retrieve → (intent=answer? → assess → update_model | sonst → explain) → END
assessbewertet mit dem kuratierten Grader (P2),update_modelschreibt das Ergebnis via BKT ins Learner-Modell (P3),explainist der einzige generative Pfad.- Ausgelöst über
POST /student/turn(api/student.py), sichtbar in der Lernsession.
cd apps/api && uv run pytest # alle Tests (Safety-Tests gegen echtes Postgres)
cd apps/web && npm run build # tsc-Typecheck + Vite-BuildGitHub Actions (.github/workflows/ci.yml) hat zwei Jobs: test (pytest mit
vorgelagertem, blockierendem Safety-Gate) und web (Frontend-Build).
Bewusst vereinfacht — vor echtem Schülerdaten-Betrieb zu ersetzen:
- Auth:
current_principalist ein Stub. Für lokales Testen akzeptiert erdev:<role>:<user>[:<student>]-Tokens, wennAUTH_DEV_MODE=1. → echtes JWT/IdP. - LLM:
llm/client.pylokaler Backend = deterministischer Stub. → lokales Modell (Qwen2.5) oder Frontier-API mit AVV/No-Training (Anonymisierung steht bereits). - Embedder:
HashingEmbedder-Stub. → echtes Embedding-Modell.
Diese Plattform verarbeitet identifizierbare Daten über Minderjährige. Datenresidenz (CH/EU, revDSG/DSGVO), PII-Minimierung im LLM-Pfad und die Isolationsgarantien sind keine optionalen Features, sondern Voraussetzung. Vor Produktivbetrieb mit echten Schülerdaten ist eine fachliche/rechtliche Prüfung gegen aktuelle Quellen erforderlich.
Die ausführliche Planung, die Fachspezifikationen, der Sicherheits-Audit und die Compliance-Details liegen lokal unter
docs/(nicht im Repository versioniert).