sendPrompt([['role' => 'user', 'content' => 'Hello']]); * echo $res['content']; */ class svcMistral extends LLMProvider { private const API_BASE = 'https://api.mistral.ai/v1'; private const API_CHAT = self::API_BASE . '/chat/completions'; private const API_MODELS = self::API_BASE . '/models'; private static array $availableModels = [ ['id' => 'mistral-large-latest', 'label' => 'Mistral Large'], ['id' => 'mistral-medium-latest', 'label' => 'Mistral Medium'], ['id' => 'mistral-small-latest', 'label' => 'Mistral Small'], ['id' => 'codestral-latest', 'label' => 'Codestral'], ['id' => 'open-mistral-nemo', 'label' => 'Mistral Nemo'], ['id' => 'open-mixtral-8x22b', 'label' => 'Mixtral 8x22B'], ]; public function __construct(array $config) { if (empty($config['secretKey'])) { throw new \Exception('Mistral secretKey is required'); } $this->apiKey = $config['secretKey']; $this->model = $config['model'] ?? 'mistral-large-latest'; $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 { $payload = $this->buildPayload($messages, $options); $response = $this->curlPost(self::API_CHAT, $this->headers(), $payload); $content = $response['choices'][0]['message']['content'] ?? ''; $usage = $response['usage'] ?? []; return $this->successResponse($content, [ 'prompt_tokens' => $usage['prompt_tokens'] ?? 0, 'completion_tokens' => $usage['completion_tokens'] ?? 0, 'total_tokens' => $usage['total_tokens'] ?? 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 { $payload = $this->buildPayload($messages, $options); $this->curlStream(self::API_CHAT, $this->headers(), $payload, function(array $chunk) use ($callback) { $delta = $chunk['choices'][0]['delta']['content'] ?? ''; if ($delta !== '') { $callback($delta); } }); return ['success' => true, 'error' => '']; } catch (\Exception $e) { return ['success' => false, 'error' => $e->getMessage()]; } } /** * Return supported Mistral models. */ public function getModels(): array { return self::$availableModels; } /** * Validate the API key via the models endpoint. */ public function testConnection(): array { try { $start = microtime(true); $ch = curl_init(self::API_MODELS); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $this->headers(), CURLOPT_TIMEOUT => $this->timeout, ]); $body = curl_exec($ch); $error = curl_error($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $latency = (int)((microtime(true) - $start) * 1000); if ($error) throw new \Exception("cURL error: $error"); $decoded = json_decode($body, true); if ($code !== 200) { $msg = $decoded['message'] ?? "HTTP $code"; throw new \Exception($msg); } 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', 'Authorization: Bearer ' . $this->apiKey, ]; } private function buildPayload(array $messages, array $options): array { $model = $options['model'] ?? $this->model; $maxTokens = (int)($options['max_tokens'] ?? $this->maxTokens); $temperature = (float)($options['temperature'] ?? $this->temperature); if ($this->systemPrompt !== '') { array_unshift($messages, ['role' => 'system', 'content' => $this->systemPrompt]); } return [ 'model' => $model, 'messages' => $messages, 'max_tokens' => $maxTokens, 'temperature' => $temperature, ]; } }