", "date": "", "lessonNumber": , "topic": "", "startTime": "", "phases": [ { "name": "", "durationMinutes": , "activity": "", "material": "", "shorthand": "", "alternativePathName": "" } ], "homework": "", "reflection": "" } ], "summary": "" } WICHTIG: Um eine bestehende Stunde zu ändern, gib exakt deren "id" aus der Eingabe zurück. Für eine neu vorgeschlagene Stunde setze "id" auf null. Erfinde niemals eine Id, die nicht in der Eingabe stand. Nutze für "alternativePathName" nur Namen aus dem mitgelieferten Katalog. PROMPT; $userContent = json_encode($body); $useFake = getenv('AI_BACKEND_FAKE_PROVIDER') === '1'; // nur für lokale Smoke-Tests, siehe README.md if ($useFake) { $provider = new FakeProvider(); $modelKey = 'fake'; } else { $providerName = $config['llm_provider']; if ($providerName !== 'anthropic') { ai_backend_fail(500, "Provider '$providerName' ist nicht implementiert."); } $modelKey = $config['anthropic']['model']; $provider = new AnthropicProvider($config['anthropic']['api_key'], $modelKey); } try { $result = $provider->sendMessage($systemPrompt, $userContent, $config['max_output_tokens']); } catch (RuntimeException $e) { ai_backend_fail(502, $e->getMessage()); } $pricing = $config['pricing'][$modelKey] ?? ($useFake ? ['input' => 0, 'output' => 0] : null); if ($pricing === null) { ai_backend_fail(500, "Kein Preis für Modell '$modelKey' konfiguriert."); } $cost = ($result['inputTokens'] / 1_000_000 * $pricing['input']) + ($result['outputTokens'] / 1_000_000 * $pricing['output']); // Guthaben abziehen und Transaktion protokollieren — mit Zeilensperre, damit zwei gleichzeitige // Anfragen desselben Nutzers das Guthaben nicht versehentlich unter 0 drücken können. $pdo->beginTransaction(); try { $stmt = $pdo->prepare('SELECT balance_usd FROM users WHERE id = ? FOR UPDATE'); $stmt->execute([$user['id']]); $currentBalance = (float) $stmt->fetchColumn(); if ($currentBalance - $cost < 0) { $pdo->rollBack(); ai_backend_fail(402, 'Guthaben würde durch diese Anfrage negativ werden.'); } $newBalance = $currentBalance - $cost; $pdo->prepare('UPDATE users SET balance_usd = ? WHERE id = ?')->execute([$newBalance, $user['id']]); $pdo->prepare( 'INSERT INTO transactions (user_id, type, model, input_tokens, output_tokens, cost_usd, balance_after) VALUES (?, "usage", ?, ?, ?, ?, ?)' )->execute([$user['id'], $modelKey, $result['inputTokens'], $result['outputTokens'], $cost, $newBalance]); $pdo->commit(); } catch (Throwable $e) { $pdo->rollBack(); throw $e; } // Erst NACH der Abrechnung validieren: die Token wurden real verbraucht, das wird auch dann // verrechnet, wenn die KI kein valides JSON geliefert hat (siehe Planungsdokument). $parsed = json_decode($result['content'], true); if (!is_array($parsed) || !isset($parsed['lessons'])) { ai_backend_fail(502, 'Die KI hat kein gültiges JSON im erwarteten Schema zurückgegeben.'); } echo json_encode($parsed);