sendPrompt([['role' => 'user', 'content' => 'Hello']]); * echo $res['content']; */ class svcGemini extends LLMProvider { private const API_BASE = 'https://generativelanguage.googleapis.com/v1beta/models'; private const API_CHAT = ':generateContent'; private const API_STREAM = ':streamGenerateContent'; private static array $availableModels = [ ['id' => 'gemini-2.5-flash', 'label' => 'Gemini 2.5 Flash'], ['id' => 'gemini-2.5-flash-lite', 'label' => 'Gemini 2.5 Flash Lite'], ['id' => 'gemini-2.0-flash', 'label' => 'Gemini 2.0 Flash'], ['id' => 'gemini-1.5-pro', 'label' => 'Gemini 1.5 Pro'], ['id' => 'gemini-1.5-flash', 'label' => 'Gemini 1.5 Flash'], ]; public function __construct(array $config) { if (empty($config['secretKey'])) { throw new \Exception('Gemini secretKey is required'); } $this->apiKey = $config['secretKey']; $this->model = $config['model'] ?? 'gemini-2.5-flash'; $this->maxTokens = (int)($config['max_tokens'] ?? 4096); $this->temperature = (float)($config['temperature'] ?? 0.7); } /** * Send a chat prompt and return the full response. */ public function sendPrompt(array $messages, array $options = []): array { try { $model = $options['model'] ?? $this->model; $url = self::API_BASE . '/' . $model . self::API_CHAT . '?key=' . $this->apiKey; $payload = $this->buildPayload($messages, $options); $response = $this->curlPost($url, $this->headers(), $payload); $content = $response['candidates'][0]['content']['parts'][0]['text'] ?? ''; $usage = $response['usageMetadata'] ?? []; return $this->successResponse($content, [ 'prompt_tokens' => $usage['promptTokenCount'] ?? 0, 'completion_tokens' => $usage['candidatesTokenCount'] ?? 0, 'total_tokens' => $usage['totalTokenCount'] ?? 0, ]); } catch (\Exception $e) { return $this->errorResponse($e->getMessage()); } } /** * Stream a chat prompt, calling $callback with each text chunk. * * @param callable $callback function(string $chunk) */ public function streamPrompt(array $messages, array $options, callable $callback): array { try { $model = $options['model'] ?? $this->model; $url = self::API_BASE . '/' . $model . self::API_STREAM . '?key=' . $this->apiKey . '&alt=sse'; $payload = $this->buildPayload($messages, $options); $this->curlStream($url, $this->headers(), $payload, function(array $chunk) use ($callback) { $text = $chunk['candidates'][0]['content']['parts'][0]['text'] ?? ''; if ($text !== '') { $callback($text); } }); return ['success' => true, 'error' => '']; } catch (\Exception $e) { return ['success' => false, 'error' => $e->getMessage()]; } } /** * Return supported Gemini models. */ public function getModels(): array { return self::$availableModels; } /** * Validate the API key with a minimal request. */ public function testConnection(): array { try { $start = microtime(true); $model = $this->model; $url = self::API_BASE . '/' . $model . self::API_CHAT . '?key=' . $this->apiKey; $payload = [ 'contents' => [['role' => 'user', 'parts' => [['text' => 'hi']]]], 'generationConfig' => ['maxOutputTokens' => 1], ]; $this->curlPost($url, $this->headers(), $payload); $latency = (int)((microtime(true) - $start) * 1000); return ['success' => true, 'latency_ms' => $latency, 'error' => '']; } catch (\Exception $e) { return ['success' => false, 'latency_ms' => 0, 'error' => $e->getMessage()]; } } // ─── Private Helpers ───────────────────────────────────────────────────── private function headers(): array { return ['Content-Type: application/json']; } private function buildPayload(array $messages, array $options): array { $maxTokens = (int)($options['max_tokens'] ?? $this->maxTokens); $temperature = (float)($options['temperature'] ?? $this->temperature); // Convert OpenAI-style messages to Gemini format // Roles: 'user' stays 'user', 'assistant' becomes 'model' $contents = []; foreach ($messages as $msg) { $contents[] = [ 'role' => $msg['role'] === 'assistant' ? 'model' : 'user', 'parts' => [['text' => $msg['content']]], ]; } $payload = [ 'contents' => $contents, 'generationConfig' => [ 'maxOutputTokens' => $maxTokens, 'temperature' => $temperature, ], ]; // Gemini uses a top-level 'systemInstruction' for system prompts if ($this->systemPrompt !== '') { $payload['systemInstruction'] = [ 'parts' => [['text' => $this->systemPrompt]], ]; } return $payload; } }