request([ 'model' => $this->settings->model ?? 'gpt-4o-mini', 'messages' => [ [ 'role' => 'system', 'content' => 'You optimize blog article content. Respond with JSON containing summary (string) and suggestions (array of strings).', ], [ 'role' => 'user', 'content' => $prompt, ], ], 'response_format' => ['type' => 'json_object'], ]); $content = data_get($response, 'choices.0.message.content'); if (! is_string($content)) { throw new RuntimeException('LLM completion response missing content.'); } $decoded = json_decode($content, true); if (! is_array($decoded)) { throw new RuntimeException('LLM completion response is not valid JSON.'); } return [ 'summary' => (string) ($decoded['summary'] ?? ''), 'suggestions' => array_values($decoded['suggestions'] ?? []), ]; } public function moderate(string $content): array { $response = $this->request([ 'model' => $this->settings->model ?? 'gpt-4o-mini', 'messages' => [ [ 'role' => 'system', 'content' => 'Moderate blog comments. Respond with JSON: {"status":"approved|rejected|needs_human","reason":"..."}', ], [ 'role' => 'user', 'content' => $content, ], ], 'response_format' => ['type' => 'json_object'], ]); $payload = data_get($response, 'choices.0.message.content'); $decoded = is_string($payload) ? json_decode($payload, true) : null; if (! is_array($decoded) || ! isset($decoded['status'])) { return [ 'status' => 'needs_human', 'reason' => 'Unable to parse moderation response.', ]; } return [ 'status' => (string) $decoded['status'], 'reason' => isset($decoded['reason']) ? (string) $decoded['reason'] : null, ]; } /** * @param array $payload * @return array */ protected function request(array $payload): array { $baseUrl = rtrim($this->settings->api_base_url ?? 'https://api.openai.com/v1', '/'); $response = Http::withToken($this->settings->api_key ?? '') ->acceptJson() ->timeout(60) ->post("{$baseUrl}/chat/completions", $payload) ->throw() ->json(); if (! is_array($response)) { throw new RuntimeException('LLM provider returned an invalid response.'); } return $response; } }