363 lines
15 KiB
PHP
Executable File
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']]);
|
|
}
|
|
|