Files
wevia-brain/s89-ai-apis/ai-rotation.php
2026-04-12 23:01:36 +02:00

363 lines
15 KiB
PHP
Executable File

<?php
/**
* AI PROVIDER ROTATION ENGINE
* Smart failover + load balancing across 11 providers
*/
header('Content-Type: application/json');
$pdo = new PDO("pgsql:host=localhost;dbname=adx_system", "admin", "admin123", [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$pdo->exec("
CREATE TABLE IF NOT EXISTS admin.ai_providers (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE,
api_url TEXT,
api_key TEXT,
model VARCHAR(255),
priority INTEGER DEFAULT 5,
is_active BOOLEAN DEFAULT true,
requests_today INTEGER DEFAULT 0,
requests_limit INTEGER DEFAULT 1000,
errors_today INTEGER DEFAULT 0,
avg_response_time FLOAT DEFAULT 0,
success_rate FLOAT DEFAULT 100,
last_used TIMESTAMP,
last_error TEXT,
cooldown_until TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS admin.ai_request_log (
id SERIAL PRIMARY KEY,
provider VARCHAR(100),
request_type VARCHAR(50),
tokens_used INTEGER,
response_time FLOAT,
success BOOLEAN,
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
");
class AIRotation {
private $pdo;
private $providers = [
'Cerebras' => ['url' => 'https://api.cerebras.ai/v1/chat/completions', 'model' => 'llama3.1-70b', 'limit' => 1000],
'Groq' => ['url' => 'https://api.groq.com/openai/v1/chat/completions', 'model' => 'llama-3.1-70b-versatile', 'limit' => 500],
'DeepSeek' => ['url' => 'https://api.deepseek.com/v1/chat/completions', 'model' => 'deepseek-chat', 'limit' => 1000],
'SambaNova' => ['url' => 'https://api.sambanova.ai/v1/chat/completions', 'model' => 'Meta-Llama-3.1-70B-Instruct', 'limit' => 500],
'Hyperbolic' => ['url' => 'https://api.hyperbolic.xyz/v1/chat/completions', 'model' => 'meta-llama/Llama-3.2-70B-Instruct', 'limit' => 500],
'Mistral' => ['url' => 'https://api.mistral.ai/v1/chat/completions', 'model' => 'mistral-large-latest', 'limit' => 500],
'Gemini' => ['url' => 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent', 'model' => 'gemini-pro', 'limit' => 1500],
'Claude' => ['url' => 'https://api.anthropic.com/v1/messages', 'model' => 'claude-3-haiku-20240307', 'limit' => 500],
'Cohere' => ['url' => 'https://api.cohere.ai/v1/chat', 'model' => 'command-r-plus', 'limit' => 1000],
'OpenAI' => ['url' => 'https://api.openai.com/v1/chat/completions', 'model' => 'gpt-4o-mini', 'limit' => 500],
'Ollama' => ['url' => 'http://88.198.4.195:11434/api/chat', 'model' => 'llama3', 'limit' => 99999]
];
public function __construct($pdo) {
$this->pdo = $pdo;
$this->initProviders();
}
private function initProviders() {
foreach ($this->providers as $name => $config) {
$this->pdo->prepare("INSERT INTO admin.ai_providers (name, api_url, model, requests_limit, priority)
VALUES (?, ?, ?, ?, ?) ON CONFLICT (name) DO UPDATE SET api_url = ?, model = ?, requests_limit = ?")
->execute([$name, $config['url'], $config['model'], $config['limit'], array_search($name, array_keys($this->providers)) + 1,
$config['url'], $config['model'], $config['limit']]);
}
}
// ============================================
// SMART PROVIDER SELECTION
// ============================================
public function getBestProvider() {
// Get available providers (not in cooldown, under limit, active)
$providers = $this->pdo->query("
SELECT * FROM admin.ai_providers
WHERE is_active = true
AND api_key IS NOT NULL AND api_key != ''
AND requests_today < requests_limit
AND (cooldown_until IS NULL OR cooldown_until < NOW())
ORDER BY
success_rate DESC,
avg_response_time ASC,
priority ASC
LIMIT 1
")->fetch(PDO::FETCH_ASSOC);
if (!$providers) {
// All providers exhausted - try to find any available
$providers = $this->pdo->query("
SELECT * FROM admin.ai_providers
WHERE is_active = true AND api_key IS NOT NULL AND api_key != ''
ORDER BY requests_today ASC, priority ASC
LIMIT 1
")->fetch(PDO::FETCH_ASSOC);
}
return $providers;
}
public function getNextProvider($excludeProvider = null) {
$exclude = $excludeProvider ? "AND name != '$excludeProvider'" : "";
return $this->pdo->query("
SELECT * FROM admin.ai_providers
WHERE is_active = true
AND api_key IS NOT NULL AND api_key != ''
AND requests_today < requests_limit
AND (cooldown_until IS NULL OR cooldown_until < NOW())
$exclude
ORDER BY priority ASC, success_rate DESC
LIMIT 1
")->fetch(PDO::FETCH_ASSOC);
}
// ============================================
// API CALL WITH AUTO-FAILOVER
// ============================================
public function call($prompt, $systemPrompt = '', $maxRetries = 3) {
$attempts = 0;
$lastError = null;
$usedProviders = [];
while ($attempts < $maxRetries) {
$provider = $this->getBestProvider();
if (!$provider || in_array($provider['name'], $usedProviders)) {
$provider = $this->getNextProvider(end($usedProviders) ?: null);
}
if (!$provider) {
return ['success' => false, 'error' => 'No available AI providers', 'attempts' => $attempts];
}
$usedProviders[] = $provider['name'];
$startTime = microtime(true);
$result = $this->callProvider($provider, $prompt, $systemPrompt);
$elapsed = microtime(true) - $startTime;
// Log request
$this->logRequest($provider['name'], 'chat', $result['tokens'] ?? 0, $elapsed, $result['success'], $result['error'] ?? null);
if ($result['success']) {
// Update provider stats
$this->updateProviderSuccess($provider['name'], $elapsed);
return [
'success' => true,
'response' => $result['content'],
'provider' => $provider['name'],
'time' => round($elapsed, 2),
'attempts' => $attempts + 1
];
}
// Handle failure
$lastError = $result['error'];
$this->updateProviderError($provider['name'], $lastError);
$attempts++;
}
return ['success' => false, 'error' => $lastError, 'attempts' => $attempts, 'tried' => $usedProviders];
}
private function callProvider($provider, $prompt, $systemPrompt) {
$name = $provider['name'];
$url = $provider['api_url'];
$key = $provider['api_key'];
$model = $provider['model'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_POST, true);
// Build request based on provider
switch ($name) {
case 'Claude':
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'x-api-key: ' . $key,
'anthropic-version: 2023-06-01'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'model' => $model,
'max_tokens' => 1024,
'system' => $systemPrompt ?: 'You are a helpful assistant.',
'messages' => [['role' => 'user', 'content' => $prompt]]
]));
break;
case 'Gemini':
$url .= '?key=' . $key;
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'contents' => [['parts' => [['text' => ($systemPrompt ? $systemPrompt . "\n\n" : '') . $prompt]]]]
]));
break;
case 'Cohere':
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $key
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'model' => $model,
'message' => $prompt,
'preamble' => $systemPrompt ?: 'You are a helpful assistant.'
]));
break;
case 'Ollama':
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'model' => $model,
'messages' => [
['role' => 'system', 'content' => $systemPrompt ?: 'You are a helpful assistant.'],
['role' => 'user', 'content' => $prompt]
],
'stream' => false
]));
break;
default: // OpenAI-compatible
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $key
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'model' => $model,
'messages' => [
['role' => 'system', 'content' => $systemPrompt ?: 'You are a helpful assistant.'],
['role' => 'user', 'content' => $prompt]
],
'max_tokens' => 1024,
'temperature' => 0.7
]));
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
return ['success' => false, 'error' => "CURL: $error"];
}
if ($httpCode >= 400) {
return ['success' => false, 'error' => "HTTP $httpCode: " . substr($response, 0, 200)];
}
$data = json_decode($response, true);
$content = $this->extractContent($name, $data);
if ($content) {
return ['success' => true, 'content' => $content, 'tokens' => $this->countTokens($data, $name)];
}
return ['success' => false, 'error' => 'Empty response'];
}
private function extractContent($provider, $data) {
switch ($provider) {
case 'Claude':
return $data['content'][0]['text'] ?? null;
case 'Gemini':
return $data['candidates'][0]['content']['parts'][0]['text'] ?? null;
case 'Cohere':
return $data['text'] ?? null;
case 'Ollama':
return $data['message']['content'] ?? null;
default:
return $data['choices'][0]['message']['content'] ?? null;
}
}
private function countTokens($data, $provider) {
return $data['usage']['total_tokens'] ?? $data['usage']['input_tokens'] ?? 0;
}
// ============================================
// STATS & MANAGEMENT
// ============================================
private function logRequest($provider, $type, $tokens, $time, $success, $error = null) {
$this->pdo->prepare("INSERT INTO admin.ai_request_log (provider, request_type, tokens_used, response_time, success, error_message) VALUES (?, ?, ?, ?, ?, ?)")
->execute([$provider, $type, $tokens, $time, $success, $error]);
$this->pdo->exec("UPDATE admin.ai_providers SET requests_today = requests_today + 1, last_used = NOW() WHERE name = '$provider'");
}
private function updateProviderSuccess($name, $time) {
$this->pdo->exec("UPDATE admin.ai_providers SET
success_rate = (success_rate * 0.9 + 100 * 0.1),
avg_response_time = (avg_response_time * 0.9 + $time * 0.1),
cooldown_until = NULL
WHERE name = '$name'");
}
private function updateProviderError($name, $error) {
$this->pdo->exec("UPDATE admin.ai_providers SET
errors_today = errors_today + 1,
success_rate = success_rate * 0.8,
last_error = '" . addslashes($error) . "',
cooldown_until = NOW() + INTERVAL '5 minutes'
WHERE name = '$name'");
}
public function setApiKey($provider, $key) {
$this->pdo->prepare("UPDATE admin.ai_providers SET api_key = ? WHERE name = ?")->execute([$key, $provider]);
return ['success' => true];
}
public function resetDailyCounters() {
$this->pdo->exec("UPDATE admin.ai_providers SET requests_today = 0, errors_today = 0");
return ['success' => true];
}
public function getStats() {
return [
'providers' => $this->pdo->query("SELECT name, is_active, requests_today, requests_limit, errors_today, success_rate, avg_response_time,
CASE WHEN api_key IS NOT NULL AND api_key != '' THEN true ELSE false END as has_key,
cooldown_until
FROM admin.ai_providers ORDER BY priority")->fetchAll(PDO::FETCH_ASSOC),
'total_requests_today' => $this->pdo->query("SELECT SUM(requests_today) FROM admin.ai_providers")->fetchColumn(),
'available_capacity' => $this->pdo->query("SELECT SUM(requests_limit - requests_today) FROM admin.ai_providers WHERE is_active = true AND api_key IS NOT NULL")->fetchColumn()
];
}
}
$rotation = new AIRotation($pdo);
$action = $_POST['action'] ?? $_GET['action'] ?? '';
switch ($action) {
case 'call':
echo json_encode($rotation->call($_POST['prompt'], $_POST['system'] ?? ''));
break;
case 'best':
echo json_encode($rotation->getBestProvider());
break;
case 'set_key':
echo json_encode($rotation->setApiKey($_POST['provider'], $_POST['api_key']));
break;
case 'reset':
echo json_encode($rotation->resetDailyCounters());
break;
case 'stats':
echo json_encode($rotation->getStats());
break;
default:
echo json_encode(['name' => 'AI Rotation Engine', 'actions' => ['call','best','set_key','reset','stats']]);
}