#!/usr/bin/env php api_key = $config['api_key'] ?? null; $this->timeout = $config['timeout'] ?? 30; $this->debug = $config['debug'] ?? false; $endpoints = $config['endpoints'] ?? $this->discoverEndpointsFromEnv('MODEL_ENDPOINT'); $ttsEndpoints = $config['tts_endpoints'] ?? $this->discoverEndpointsFromEnv('TTS_ENDPOINT'); if ($this->debug) { echo "[DEBUG] Initialized with " . count($endpoints) . " endpoint(s)\n"; } $this->discoverModels($endpoints); $this->tts_endpoints = $ttsEndpoints; } public function listModels() { return $this->models; } public function chat($messages, $options = []) { $model = $options['model'] ?? null; $modelInfo = $this->resolveModel($model); $payload = [ 'model' => $modelInfo['id'], 'messages' => $messages, 'max_tokens' => $options['max_tokens'] ?? 100, 'temperature' => $options['temperature'] ?? 0.7 ]; $response = $this->httpRequest($modelInfo['endpoint'] . '/chat/completions', 'POST', $payload); return json_decode($response, true); } public function chatStream($messages, $options, $callback) { $model = $options['model'] ?? null; $modelInfo = $this->resolveModel($model); $payload = [ 'model' => $modelInfo['id'], 'messages' => $messages, 'max_tokens' => $options['max_tokens'] ?? 500, 'temperature' => $options['temperature'] ?? 0.7, 'stream' => true ]; $url = $modelInfo['endpoint'] . '/chat/completions'; $ch = curl_init($url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); $buffer = ''; curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($curl, $data) use ($callback, &$buffer) { $buffer .= $data; $lines = explode("\n", $buffer); $buffer = array_pop($lines); foreach ($lines as $line) { if (strpos($line, 'data: ') === 0) { $json = substr($line, 6); if (trim($json) === '[DONE]') { return strlen($data); } $chunk = json_decode($json, true); if ($chunk) { call_user_func($callback, $chunk); } } } return strlen($data); }); curl_exec($ch); if (curl_errno($ch)) { throw new Exception(curl_error($ch)); } curl_close($ch); } public function tts($text, $voice = 'alloy', $model = 'tts-1') { if (empty($this->tts_endpoints)) { throw new Exception('No TTS endpoints available'); } $payload = [ 'model' => $model, 'voice' => $voice, 'input' => $text ]; return $this->httpRequest($this->tts_endpoints[0] . '/audio/speech', 'POST', $payload); } private function discoverEndpointsFromEnv($prefix) { $endpoints = []; for ($i = 1; $i < 10000; $i++) { $endpoint = getenv("{$prefix}_{$i}"); if ($endpoint === false) break; $endpoints[] = $endpoint; } return $endpoints; } private function discoverModels($endpoints) { foreach ($endpoints as $endpoint) { if ($this->debug) { echo "[DEBUG] Discovering from: $endpoint\n"; } try { $response = $this->httpRequest("$endpoint/models", 'GET'); $data = json_decode($response, true); foreach ($data['data'] as $model) { $maxTokens = $model['max_model_len'] ?? 8192; $this->models[] = [ 'id' => $model['id'], 'endpoint' => $endpoint, 'max_tokens' => $maxTokens ]; if ($this->debug) { echo "[DEBUG] Discovered: {$model['id']}\n"; } } } catch (Exception $e) { if ($this->debug) { echo "[DEBUG] Error: {$e->getMessage()}\n"; } } } } private function resolveModel($model) { if (empty($this->models)) { throw new Exception('No models available'); } if ($model === null) { return $this->models[0]; } foreach ($this->models as $m) { if ($m['id'] === $model) { return $m; } } throw new Exception("Model '$model' not found"); } private function httpRequest($url, $method, $payload = null) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); if ($method === 'POST' && $payload) { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); } if ($this->api_key) { curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', "Authorization: Bearer {$this->api_key}" ]); } $response = curl_exec($ch); if (curl_errno($ch)) { throw new Exception(curl_error($ch)); } curl_close($ch); return $response; } } // Demo when run as script if (basename(__FILE__) === basename($_SERVER['PHP_SELF'])) { echo "=== UncloseAI PHP Client (with Streaming) ===\n\n"; $client = new UncloseAI(['debug' => true]); if (empty($client->listModels())) { echo "ERROR: No models discovered. Set environment variables:\n"; echo " MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.\n"; exit(1); } $models = $client->listModels(); echo "\nDiscovered " . count($models) . " model(s):\n"; foreach ($models as $m) { echo " - {$m['id']} (max_tokens: {$m['max_tokens']})\n"; } echo "\n"; // Non-streaming chat echo "=== Non-Streaming Chat ===\n"; $response = $client->chat([ ['role' => 'system', 'content' => 'You are a helpful AI assistant.'], ['role' => 'user', 'content' => 'Explain quantum computing in one sentence.'] ]); echo "Response: " . $response['choices'][0]['message']['content'] . "\n\n"; // Streaming chat echo "=== Streaming Chat ===\n"; $modelId = count($models) > 1 ? $models[1]['id'] : null; echo "Model: " . ($modelId ?? $models[0]['id']) . "\n"; echo "Response: "; $client->chatStream([ ['role' => 'system', 'content' => 'You are a coding assistant.'], ['role' => 'user', 'content' => 'Write a PHP function to check if a number is prime'] ], ['model' => $modelId, 'max_tokens' => 200], function($chunk) { if (isset($chunk['choices'][0]['delta']['content'])) { echo $chunk['choices'][0]['delta']['content']; } }); echo "\n\n"; // TTS if (!empty($client->listModels())) { echo "=== TTS Speech Generation ===\n"; try { $audioData = $client->tts('Hello from UncloseAI PHP client! This demonstrates streaming support.'); file_put_contents('speech.mp3', $audioData); echo "[OK] Speech file created: speech.mp3 (" . strlen($audioData) . " bytes)\n\n"; } catch (Exception $e) { echo "[ERROR] TTS Error: " . $e->getMessage() . "\n\n"; } } echo "=== Examples Complete ===\n"; }