providerId = $providerId; $this->providerName = $providerName ?: ucfirst($providerId); } /** * {@inheritdoc} */ public function chat(string $systemPrompt, string $userPrompt, array $options = []): string { $apiKey = get_system_config("ai_provider_{$this->providerId}_key", ''); $baseUrl = get_system_config("ai_provider_{$this->providerId}_base_url", 'https://api.openai.com/v1'); $model = $options['model'] ?? get_system_config('ai_default_model', 'gpt-3.5-turbo'); $url = rtrim($baseUrl, '/') . '/chat/completions'; $data = [ 'model' => $model, 'messages' => [ ['role' => 'system', 'content' => $systemPrompt], ['role' => 'user', 'content' => $userPrompt], ], ]; if (isset($options['temperature'])) { $data['temperature'] = (float) $options['temperature']; } if (isset($options['max_tokens'])) { $data['max_tokens'] = (int) $options['max_tokens']; } $response = $this->httpPost($url, $data, [ 'Authorization: Bearer ' . $apiKey, 'Content-Type: application/json', ]); if ($response === false) { return ''; } $result = json_decode($response, true); if (!$result || isset($result['error'])) { return ''; } return $result['choices'][0]['message']['content'] ?? ''; } /** * {@inheritdoc} */ public function chatJson(string $systemPrompt, string $userPrompt, array $options = []): array { $jsonInstruction = "\n\n请严格按照JSON格式返回结果,不要包含任何其他文字说明。"; $content = $this->chat($systemPrompt . $jsonInstruction, $userPrompt, $options); if (empty($content)) { return []; } // 尝试提取JSON内容(可能包裹在markdown代码块中) if (preg_match('/```(?:json)?\s*([\s\S]*?)```/', $content, $matches)) { $content = $matches[1]; } $decoded = json_decode(trim($content), true); return is_array($decoded) ? $decoded : []; } /** * {@inheritdoc} */ public function getProviderName(): string { return $this->providerName; } /** * {@inheritdoc} */ public function getModels(): array { $apiKey = get_system_config("ai_provider_{$this->providerId}_key", ''); $baseUrl = get_system_config("ai_provider_{$this->providerId}_base_url", 'https://api.openai.com/v1'); if (empty($apiKey)) { return []; } $url = rtrim($baseUrl, '/') . '/models'; $response = $this->httpGet($url, [ 'Authorization: Bearer ' . $apiKey, ]); if ($response === false) { return []; } $result = json_decode($response, true); if (!$result || !isset($result['data'])) { return []; } $models = []; foreach ($result['data'] as $model) { $models[] = [ 'id' => $model['id'] ?? '', 'name' => $model['id'] ?? '', 'owned_by' => $model['owned_by'] ?? '', ]; } return $models; } /** * {@inheritdoc} */ public function testConnection(): bool { $apiKey = get_system_config("ai_provider_{$this->providerId}_key", ''); if (empty($apiKey)) { return false; } try { $result = $this->chat( 'You are a test assistant.', 'Reply with exactly: OK', ['max_tokens' => 5] ); return !empty($result); } catch (\Exception $e) { return false; } } /** * HTTP POST 请求. * * @param string $url 请求地址 * @param array $data 请求数据 * @param array $headers 请求头 * * @return string|false */ protected function httpPost(string $url, array $data, array $headers = []) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_TIMEOUT, 60); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode >= 400) { return false; } return $response; } /** * HTTP GET 请求. * * @param string $url 请求地址 * @param array $headers 请求头 * * @return string|false */ protected function httpGet(string $url, array $headers = []) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_TIMEOUT, 30); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode >= 400) { return false; } return $response; } }