Files
LehrerApp/ai-backend/README.md
T
admin 1928916eac KI-Backend: Umfangs-Umschalter + Prompt Caching (4.5.15/4.5.16)
Umfangs-Umschalter: Checkbox "Auch bestehende Stundeninhalte anpassen" im
AiAssistDialog steuert, ob die KI bestehende Stunden inhaltlich ändern darf oder
die Einheit nur um neue Stunden erweitern soll. Zweifach durchgesetzt (Systemprompt
+ hartes client-seitiges Verwerfen in ApplyResponse), nicht nur der KI-Antwort
vertraut. Dabei auch einen Bug gefixt: eine bereits "Durchgeführt" markierte Stunde
wurde durch eine übernommene KI-Änderung stillschweigend auf "Geplant" zurückgesetzt.

Prompt Caching: der Systemprompt ist jetzt vollständig statisch (Voraussetzung für
Caching) und wird von AnthropicProvider.php als "cache_control: ephemeral" markiert
— wiederholte Anfragen innerhalb der 5-Minuten-TTL zahlen nur den reduzierten
Cache-Read-Preis. transactions-Tabelle und Preistabelle um Cache-Token-Spalten
erweitert, Migration für bereits deployte Installationen beigelegt.
2026-08-16 16:06:52 +02:00

7.7 KiB

KI-Backend (TODO 4.5.9)

Kleines PHP-Zwischenelement, das die KI-gestützte Planungsunterstützung des Desktop-Clients absichert: der LLM-API-Key liegt nur hier auf dem Server, der Desktop-Client bekommt nur ein eigenes Bearer-Token gegen dieses Backend und ein pro Nutzer geführtes Guthaben. Deployment macht der Nutzer selbst — dieses README beschreibt die nötigen Schritte.

Voraussetzungen

  • PHP 8.1 oder neuer (nutzt never-Rückgabetypen, match, First-Class-Callable-Syntax nicht, aber typisierte Properties/Enums nicht zwingend — 8.1 ist die sichere Untergrenze).
  • PHP-Erweiterungen: pdo_mysql, curl, json (bei den meisten Hosting-Paketen bereits dabei).
  • Eine MySQL- oder MariaDB-Datenbank.
  • Ein API-Key für Anthropic (https://console.anthropic.com).

Einrichtung

  1. Datenbank anlegen und schema.sql importieren:

    mysql -u <user> -p <datenbankname> < schema.sql
    
  2. config.example.php nach config.php kopieren und ausfüllen (DB-Zugang, Anthropic-API-Key, ggf. die Preistabelle gegen die aktuelle Anthropic-Preisseite prüfen — Preise ändern sich). config.php ist in .gitignore und darf nie committet werden.

  3. Falls du die Web-Variante aus Schritt 5 nutzen willst (kein PHP-CLI/SSH auf dem Hosting): setup_secret in config.php auf einen langen Zufallswert setzen (z.B. per openssl rand -hex 32 erzeugt, oder ein beliebiger Passwortgenerator).

  4. Den kompletten ai-backend/-Ordner auf den PHP-Server hochladen. Empfehlung: config.php, db.php, schema.sql, providers/ und scripts/ außerhalb des öffentlichen Webroots ablegen, falls das Hosting das erlaubt (Pfade in den require-Aufrufen entsprechend anpassen) — robuster als sich allein auf die mitgelieferte .htaccess zu verlassen, die nur bei Apache mit aktiviertem mod_rewrite/erlaubten .htaccess-Overrides greift.

  5. Ersten Nutzer anlegen (weitere Lehrer später genauso) — zwei Varianten, je nachdem was das Hosting hergibt:

    Mit PHP-CLI/SSH-Zugriff (scripts/create-user.php):

    php scripts/create-user.php sebastian "einStarkesPasswort" 10.00
    

    Der dritte Parameter ist das Startguthaben in USD, optional (Standard 0).

    Ohne CLI/SSH (übliches Shared Hosting, nur FTP/Webspace) — der Server läuft nach Schritt 4 bereits, also einmalig von deinem eigenen Rechner aus aufrufen:

    curl -X POST https://DEINE-DOMAIN/setup-user.php \
      -d '{"secret":"<setup_secret aus config.php>","username":"sebastian","password":"einStarkesPasswort","balanceUsd":10.00}'
    

    Direkt danach setup-user.php per FTP wieder vom Server löschen — sonst bleibt ein Endpunkt erreichbar, der (mit dem Geheimnis) beliebige Nutzer anlegen kann. Ein erneuter Aufruf mit demselben Benutzernamen aktualisiert Passwort/Guthaben, falls z.B. das Guthaben beim ersten Versuch vergessen wurde — dafür braucht es die Datei aber kein zweites Mal auf dem Server, einfach vor dem Löschen alle nötigen Nutzer in einer Sitzung anlegen.

  6. In LehrerApp.Desktop/AppBootstrapper.cs die Konstante AiBackendUrl auf die tatsächlich deployte Domain setzen und die App neu bauen.

  7. In der App unter Einstellungen → KI-Unterstützung aktivieren und mit dem angelegten Nutzer anmelden.

Update für bereits deployte Installationen (Prompt-Caching-Nachtrag)

Falls ai-backend/ schon einmal deployt wurde (schema.sql lief bereits, config.php existiert schon): zwei Schritte nötig, damit Abrechnung und plan.php mit dem neuen Prompt Caching funktionieren.

  1. Migration einmalig einspielen (ergänzt zwei neue, nicht-destruktive Spalten):
    mysql -u <user> -p <datenbankname> < migrations/2026-08-add-cache-tokens.sql
    
  2. In der bestehenden config.php bei jedem Preistabellen-Eintrag cache_write/cache_read ergänzen (siehe config.example.php für aktuelle Richtwerte) — ohne diese Ergänzung wird der Cache-Anteil einfach mit 0 USD abgerechnet (kein Fehler, aber auch keine Kostenersparnis).

Danach alle geänderten Dateien (plan.php, db.php, providers/, .htaccess, falls noch nicht aktuell) erneut hochladen.

Prompt Caching

Der Systemprompt in plan.php ist vollständig statisch (identisch bei jeder Anfrage, jedes Nutzers). AnthropicProvider.php markiert ihn deshalb als cacheable — wiederholte Anfragen innerhalb der Anthropic-Cache-TTL (Standard: 5 Minuten) zahlen für diesen Anteil nur den stark reduzierten "Cache-Read"-Preis statt des vollen Input-Preises; der erste Aufruf nach TTL-Ablauf zahlt einen kleinen Aufpreis fürs Neuschreiben des Caches. Das passiert automatisch, ohne Zutun — nichts an der Bedienung der App ändert sich dadurch. Beobachtbar ist der Effekt in der transactions-Tabelle (cache_creation_input_tokens vs. cache_read_input_tokens).

Smoke-Test ohne echten API-Key

plan.php liest die Umgebungsvariable AI_BACKEND_FAKE_PROVIDER — bei 1 wird statt eines echten Anthropic-Aufrufs providers/FakeProvider.php verwendet (liefert eine feste, schema-valide Test-Antwort). Damit lassen sich Login, Guthabenprüfung und die 402-Pfade lokal prüfen, ohne echte Kosten zu verursachen:

AI_BACKEND_FAKE_PROVIDER=1 php -S localhost:8000 -t .

Dann z.B.:

curl -X POST http://localhost:8000/login.php \
  -d '{"username":"sebastian","password":"einStarkesPasswort"}'

curl http://localhost:8000/status.php -H "Authorization: Bearer <token aus login.php>"

Wichtig: AI_BACKEND_FAKE_PROVIDER niemals auf dem produktiven Server setzen — sonst bekommt die App nur die feste Test-Antwort statt echter KI-Vorschläge.

Fehlerbehebung: "Anmeldung abgelaufen" direkt nach dem Anmelden

Login funktioniert, aber jede Anfrage danach (Guthaben, KI-Anfrage) meldet sofort eine abgelaufene/ungültige Anmeldung? Typische Ursache: viele Apache/PHP-FPM-Hosting-Setups reichen den Authorization-Header standardmäßig gar nicht erst an PHP durch — login.php braucht ihn nicht, aber status.php/plan.php schon, und der kommt dort leer an.

Dagegen sind bereits zwei Absicherungen eingebaut:

  • .htaccess erzwingt die Weiterleitung des Headers (RewriteRule .* - [E=HTTP_AUTHORIZATION:%1]).
  • db.php liest zusätzlich REDIRECT_HTTP_AUTHORIZATION und getallheaders() als Fallback.

Falls es trotzdem weiter auftritt: .htaccess-Overrides sind auf manchem Hosting serverseitig deaktiviert (AllowOverride None), oder es läuft nginx statt Apache (dort gilt .htaccess grundsätzlich nicht) — dann hilft nur eine serverseitige Konfiguration durch den Hoster/Support (z.B. bei nginx ein fastcgi_param HTTP_AUTHORIZATION $http_authorization;).

Was hiermit NICHT geprüft ist

  • Ob Anthropic zuverlässig valides JSON im erwarteten Schema liefert (reine Prompt-Qualitätsfrage, nur mit dem echten API-Key zu beurteilen).
  • Ob die berechneten Kosten exakt mit der tatsächlichen Anthropic-Abrechnung übereinstimmen.
  • TLS/.htaccess-Wirksamkeit und PHP-Version/Erweiterungen auf dem tatsächlichen Hosting.
  • Die komplette Kette Desktop → dieses Backend → Anthropic unter echten Netzwerkbedingungen.
  • Ob Prompt Caching tatsächlich greift (cache_read_input_tokens > 0 bei einer zweiten Anfrage innerhalb von 5 Minuten) — der FakeProvider simuliert kein Caching, das lässt sich nur gegen die echte Anthropic-API beobachten (z.B. per Blick in die transactions-Tabelle).

Guthaben aufladen

Für den aktuellen Umfang (ein bis wenige Nutzer) reicht ein manueller SQL-Befehl:

UPDATE users SET balance_usd = balance_usd + 10.00 WHERE username = 'sebastian';
INSERT INTO transactions (user_id, type, cost_usd, balance_after)
  SELECT id, 'topup', -10.00, balance_usd FROM users WHERE username = 'sebastian';

(Kein Admin-UI in dieser Ausbaustufe — bei Bedarf später ergänzbar.)