Update im KI-Backend für pdf-import
CI / build-and-test (push) Canceled after 0s

This commit is contained in:
2026-08-31 23:20:19 +02:00
parent e2e2bfb854
commit 4b4a746e17
5 changed files with 40 additions and 5 deletions
+36
View File
@@ -77,6 +77,42 @@ function ai_backend_fail(int $httpStatus, string $message): never
exit;
}
/**
* Dekodiert eine JSON-Antwort des LLM. Modelle setzen trotz entsprechender Anweisung gelegentlich
* Markdown-Codezäune oder einen kurzen Begleittext um das eigentliche JSON. Diese rein
* syntaktischen Zusätze sollen eine ansonsten gültige Antwort nicht unbrauchbar machen.
*/
function ai_backend_decode_json_response(string $content): ?array
{
$content = trim($content, "\xEF\xBB\xBF \t\n\r\0\x0B");
$candidates = [$content];
if (preg_match('/```(?:json)?\s*([\s\S]*?)\s*```/i', $content, $match) === 1) {
$candidates[] = trim($match[1]);
}
$firstBrace = strpos($content, '{');
$lastBrace = strrpos($content, '}');
if ($firstBrace !== false && $lastBrace !== false && $lastBrace >= $firstBrace) {
$candidates[] = substr($content, $firstBrace, $lastBrace - $firstBrace + 1);
}
foreach (array_unique($candidates) as $candidate) {
$decoded = json_decode($candidate, true);
if (is_array($decoded) && json_last_error() === JSON_ERROR_NONE) {
return $decoded;
}
}
error_log(sprintf(
'LLM JSON parse failed: %s; length=%d; sha256=%s',
json_last_error_msg(),
strlen($content),
hash('sha256', $content)
));
return null;
}
/**
* Ruft das konfigurierte LLM (oder den FakeProvider für lokale Tests, siehe README.md) auf und
* verrechnet die echten Token-Kosten gegen das Guthaben des Nutzers. Gemeinsame Logik für jeden
+1 -1
View File
@@ -60,7 +60,7 @@ PROMPT;
$userContent = json_encode($body);
$result = ai_backend_call_and_charge($pdo, $config, $user, $systemPrompt, $userContent);
$parsed = json_decode($result['content'], true);
$parsed = ai_backend_decode_json_response($result['content']);
if (!is_array($parsed) || !isset($parsed['explanation'])) {
ai_backend_fail(502, 'Die KI hat kein gültiges JSON im erwarteten Schema zurückgegeben.');
}
+1 -1
View File
@@ -82,7 +82,7 @@ PROMPT;
$userContent = json_encode($body);
$result = ai_backend_call_and_charge($pdo, $config, $user, $systemPrompt, $userContent);
$parsed = json_decode($result['content'], true);
$parsed = ai_backend_decode_json_response($result['content']);
if (!is_array($parsed) || !isset($parsed['procedure'])) {
ai_backend_fail(502, 'Die KI hat kein gültiges JSON im erwarteten Schema zurückgegeben.');
}
+1 -2
View File
@@ -48,7 +48,7 @@ PROMPT;
$result = ai_backend_call_and_charge($pdo, $config, $user, $systemPrompt,
json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
$parsed = json_decode($result['content'], true);
$parsed = ai_backend_decode_json_response($result['content']);
if (!is_array($parsed) || !is_array($parsed['classifications'] ?? null)) {
ai_backend_fail(502, 'Die KI hat kein gültiges Klassifikations-JSON geliefert.');
}
@@ -78,4 +78,3 @@ foreach ($parsed['classifications'] as $classification) {
}
echo json_encode(['classifications' => $parsed['classifications']], JSON_UNESCAPED_UNICODE);
+1 -1
View File
@@ -161,7 +161,7 @@ $result = ai_backend_call_and_charge($pdo, $config, $user, $systemPrompt, $userC
// 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);
$parsed = ai_backend_decode_json_response($result['content']);
if (!is_array($parsed) || !isset($parsed['lessons'])) {
ai_backend_fail(502, 'Die KI hat kein gültiges JSON im erwarteten Schema zurückgegeben.');
}