'https://api.x.ai/v1/responses', 'model' => 'grok-4.7'] : ['url' => 'https://api.openai.com/v1/responses', 'model' => 'gpt-4.1-mini']; if (trim($apiKey) === '') { throw new \RuntimeException('AI_NOT_CONFIGURED'); } if (!in_array($provider, ['openai', 'grok'], true)) { throw new \RuntimeException('Nieprawidłowy dostawca AI: ' . $provider); } $input = "Oceń odpowiedź ucznia na pytanie otwarte i ustal liczbę uznanych punktów.\n" . "Maksymalna liczba punktów za to pytanie: " . max(0, $maxPoints) . ".\n" . "Przyznaj liczbę całkowitą od 0 do maksymalnej liczby punktów. Przyznaj punkty proporcjonalnie za poprawne elementy odpowiedzi; pełne punkty za komplet, część punktów za częściową odpowiedź. Nie przekraczaj limitu.\n\n" . "Pytanie:\n" . $question . "\n\n" . "Wzorzec odpowiedzi:\n" . $pattern . "\n\n" . "Odpowiedź ucznia:\n" . ($answer !== '' ? $answer : 'Brak odpowiedzi') . "\n\n" . "Zwróć wyłącznie poprawny obiekt JSON w formacie {\"awarded_points\": liczba_całkowita, \"feedback\": \"krótka ocena po polsku\"}. W ocenie opisz poprawne i brakujące elementy. Nie zmieniaj wzorca odpowiedzi."; $payload = json_encode([ 'model' => $config['model'], 'input' => $input, 'temperature' => 0.1, ], JSON_UNESCAPED_UNICODE); $response = HttpFactory::getHttp()->post( $config['url'], $payload, [ 'Authorization' => 'Bearer ' . trim($apiKey), 'Content-Type' => 'application/json', ], 60 ); $body = json_decode((string) $response->body, true); if ((int) $response->code < 200 || (int) $response->code >= 300) { throw new \RuntimeException((string) ($body['error']['message'] ?? ('HTTP ' . (int) $response->code))); } $text = $this->extractText($body); if ($text === '') { throw new \RuntimeException('Brak treści odpowiedzi AI.'); } $jsonText = preg_replace('/^```(?:json)?\s*|\s*```$/i', '', trim($text)); $result = json_decode($jsonText, true); if (!is_array($result) || !isset($result['awarded_points']) || !is_numeric($result['awarded_points'])) { $start = strpos($jsonText, '{'); $end = strrpos($jsonText, '}'); if ($start !== false && $end !== false && $end > $start) { $result = json_decode(substr($jsonText, $start, $end - $start + 1), true); } } if (!is_array($result) || !isset($result['awarded_points']) || !is_numeric($result['awarded_points'])) { throw new \RuntimeException('AI zwróciło nieprawidłowy format oceny i punktów.'); } return [ 'awarded_points' => min(max(0, $maxPoints), max(0, (int) $result['awarded_points'])), 'feedback' => trim((string) ($result['feedback'] ?? '')), ]; } private function extractText(array $body): string { if (!empty($body['output_text']) && is_string($body['output_text'])) { return trim($body['output_text']); } $parts = []; foreach ((array) ($body['output'] ?? []) as $output) { foreach ((array) ($output['content'] ?? []) as $content) { if (!empty($content['text']) && is_string($content['text'])) { $parts[] = $content['text']; } } } return trim(implode("\n", $parts)); } }