uncloseai.com/public/languages/php/uncloseai.php

258 lines
8.2 KiB
PHP

#!/usr/bin/env php
<?php
// PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
// Copyright 2025 TimeHexOn & foxhop & russell@unturf
// https://www.permacomputer.com
/**
* UncloseAI - PHP client for OpenAI-compatible APIs with streaming support
*/
class UncloseAI {
private $models = [];
private $tts_endpoints = [];
private $api_key;
private $timeout;
private $debug;
public function __construct($config = []) {
$this->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";
}