Files
html/api/openclaw-proxy.php
opus eb62f917ee
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
auto-sync-20260416-2024
2026-04-16 22:24:38 +02:00

163 lines
11 KiB
PHP

<?php
require_once __DIR__.'/weval-brand-guard.php';
// SMART_ROUTING: Auto-fallback chain
// groq→cerebras→sambanova, cerebras→groq→sambanova, etc.
// TODO: Implement provider health check + auto-retry on 429/500/timeout
/**
* OpenClaw Multi-Provider Proxy API v2
* 10 providers, 35+ models, streaming SSE
*/
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit; }
@require_once '/opt/wevads/vault/credentials.php';
// Auto-loaded keys from secrets.env
if (!defined('TOGETHER_KEY')) { $_k=trim(shell_exec("grep TOGETHER_KEY /etc/weval/secrets.env 2>/dev/null | cut -d= -f2")); if($_k) define('TOGETHER_KEY',$_k); }
if (!defined('COHERE_KEY')) { $_k=trim(shell_exec("grep COHERE_KEY /etc/weval/secrets.env 2>/dev/null | cut -d= -f2")); if($_k) define('COHERE_KEY',$_k); }
if (!defined('NVIDIA_NIM_KEY')) { $_k=trim(shell_exec("grep NVIDIA_NIM_KEY /etc/weval/secrets.env 2>/dev/null | cut -d= -f2")); if($_k) define('NVIDIA_NIM_KEY',$_k); }
if (!defined('OPENROUTER_KEY')) { $_k=trim(shell_exec("grep OPENROUTER_KEY /etc/weval/secrets.env 2>/dev/null | cut -d= -f2")); if($_k) define('OPENROUTER_KEY',$_k); }
if (!defined('HF_TOKEN')) { $_k=trim(shell_exec("grep HF_TOKEN /etc/weval/secrets.env 2>/dev/null | cut -d= -f2")); if($_k) define('HF_TOKEN',$_k); }
if (!defined('ZHIPU_KEY')) { $_k=trim(shell_exec("grep ZHIPU_KEY /etc/weval/secrets.env 2>/dev/null | cut -d= -f2")); if($_k) define('ZHIPU_KEY',$_k); }
if (!defined('REPLICATE_KEY')) { $_k=trim(shell_exec("grep REPLICATE_KEY /etc/weval/secrets.env 2>/dev/null | cut -d= -f2")); if($_k) define('REPLICATE_KEY',$_k); }
$PROVIDERS = [
'groq' => ['name'=>'Groq','tier'=>'free','speed'=>'ultra',
'base_url'=>'https://api.groq.com/openai/v1',
'api_key'=>defined('GROQ_KEY')?GROQ_KEY:'',
'models'=>['llama-3.3-70b-versatile'=>'Llama 3.3 70B','llama-3.1-8b-instant'=>'Llama 3.1 8B Instant','meta-llama/llama-4-scout-17b-16e-instruct'=>'Llama 4 Scout 17B Vision','gemma2-9b-it'=>'Gemma 2 9B','mixtral-8x7b-32768'=>'Mixtral 8x7B']],
'cerebras' => ['name'=>'Cerebras','tier'=>'free','speed'=>'ultra',
'base_url'=>'https://api.cerebras.ai/v1',
'api_key'=>defined('CEREBRAS_KEY')?CEREBRAS_KEY:'',
'models'=>['qwen-3-235b-a22b-instruct-2507'=>'Qwen 3 235B','llama-3.3-70b'=>'Llama 3.3 70B','llama-3.1-8b'=>'Llama 3.1 8B']],
'sambanova' => ['name'=>'SambaNova','tier'=>'free','speed'=>'ultra',
'base_url'=>'https://api.sambanova.ai/v1',
'api_key'=>defined('SAMBANOVA_KEY')?SAMBANOVA_KEY:'',
'models'=>['Meta-Llama-3.3-70B-Instruct'=>'Llama 3.3 70B','Meta-Llama-3.1-8B-Instruct'=>'Llama 3.1 8B','DeepSeek-V3.1'=>'DeepSeek V3.1','DeepSeek-R1'=>'DeepSeek R1 Reasoning']],
'mistral' => ['name'=>'Mistral AI','tier'=>'free','speed'=>'fast',
'base_url'=>'https://api.mistral.ai/v1',
'api_key'=>defined('MISTRAL_KEY')?MISTRAL_KEY:'',
'models'=>['mistral-small-latest'=>'Mistral Small','open-mistral-nemo'=>'Mistral Nemo 12B','codestral-latest'=>'Codestral Code']],
'deepseek' => ['name'=>'DeepSeek','tier'=>'free','speed'=>'fast',
'base_url'=>'https://api.deepseek.com',
'api_key'=>defined('DEEPSEEK_KEY')?DEEPSEEK_KEY:'',
'models'=>['deepseek-chat'=>'DeepSeek V3 Chat','deepseek-reasoner'=>'DeepSeek R1 Reasoner']],
'gemini' => ['name'=>'Google Gemini','tier'=>'free','speed'=>'fast',
'base_url'=>'https://generativelanguage.googleapis.com/v1beta/openai',
'api_key'=>defined('GEMINI_KEY')?GEMINI_KEY:'',
'models'=>['gemini-2.0-flash'=>'Gemini 2.0 Flash','gemini-2.0-flash-lite'=>'Gemini 2.0 Flash Lite','gemini-1.5-pro'=>'Gemini 1.5 Pro']],
'alibaba' => ['name'=>'Alibaba Qwen','tier'=>'free','speed'=>'fast',
'base_url'=>'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',
'api_key'=>defined('DASHSCOPE_KEY')?DASHSCOPE_KEY:'',
'models'=>['qwen-turbo-latest'=>'Qwen Turbo','qwen-plus-latest'=>'Qwen Plus','qwen-max'=>'Qwen Max']],
'anthropic' => ['name'=>'Anthropic Claude','tier'=>'paid','speed'=>'fast',
'base_url'=>'https://api.anthropic.com/v1',
'api_key'=>defined('ANTHROPIC_KEY')?ANTHROPIC_KEY:'',
'models'=>['claude-sonnet-4-20250514'=>'Claude Sonnet 4','claude-haiku-4-5-20251001'=>'Claude Haiku 4.5'],
'custom_format'=>'anthropic'],
'together' => ['name'=>'Together AI','tier'=>'free','speed'=>'fast',
'base_url'=>'https://api.together.xyz/v1',
'api_key'=>defined('TOGETHER_KEY')?TOGETHER_KEY:'',
'models'=>['meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo'=>'Llama 3.1 70B Turbo','mistralai/Mixtral-8x22B-Instruct-v0.1'=>'Mixtral 8x22B','Qwen/Qwen2.5-72B-Instruct-Turbo'=>'Qwen 2.5 72B']],
'cohere' => ['name'=>'Cohere','tier'=>'free','speed'=>'fast',
'base_url'=>'https://api.cohere.com/v2',
'api_key'=>defined('COHERE_KEY')?COHERE_KEY:'',
'models'=>['command-r-plus'=>'Command R+','command-r'=>'Command R','command-light'=>'Command Light'],
'custom_format'=>'cohere'],
'nvidia' => ['name'=>'NVIDIA NIM','tier'=>'free','speed'=>'fast',
'base_url'=>'https://integrate.api.nvidia.com/v1',
'api_key'=>defined('NVIDIA_NIM_KEY')?NVIDIA_NIM_KEY:'',
'models'=>['meta/llama-3.3-70b-instruct'=>'Llama 3.3 70B','nvidia/llama-3.1-nemotron-70b-instruct'=>'Nemotron 70B','deepseek-ai/deepseek-r1'=>'DeepSeek R1']],
'openrouter' => ['name'=>'OpenRouter','tier'=>'free','speed'=>'fast',
'base_url'=>'https://openrouter.ai/api/v1',
'api_key'=>defined('OPENROUTER_KEY')?OPENROUTER_KEY:'',
'models'=>['meta-llama/llama-3.3-70b-instruct:free'=>'Llama 3.3 70B Free','google/gemma-2-9b-it:free'=>'Gemma 2 9B Free','qwen/qwen-2.5-72b-instruct:free'=>'Qwen 2.5 72B Free']],
'huggingface' => ['name'=>'HuggingFace','tier'=>'free','speed'=>'slow',
'base_url'=>'https://api-inference.huggingface.co/models',
'api_key'=>defined('HF_TOKEN')?HF_TOKEN:'',
'models'=>['meta-llama/Llama-3.1-8B-Instruct'=>'Llama 3.1 8B','mistralai/Mistral-7B-Instruct-v0.3'=>'Mistral 7B','Qwen/Qwen2.5-7B-Instruct'=>'Qwen 2.5 7B'],
'custom_format'=>'huggingface'],
'zhipu' => ['name'=>'ZhiPu GLM','tier'=>'free','speed'=>'fast',
'base_url'=>'https://open.bigmodel.cn/api/paas/v4',
'api_key'=>defined('ZHIPU_KEY')?ZHIPU_KEY:'',
'models'=>['glm-4-flash'=>'GLM-4 Flash','glm-4-air'=>'GLM-4 Air','glm-4'=>'GLM-4']],
'replicate' => ['name'=>'Replicate','tier'=>'free','speed'=>'slow',
'base_url'=>'https://api.replicate.com/v1',
'api_key'=>defined('REPLICATE_KEY')?REPLICATE_KEY:'',
'models'=>['meta/meta-llama-3-70b-instruct'=>'Llama 3 70B','mistralai/mixtral-8x7b-instruct-v0.1'=>'Mixtral 8x7B'],
'custom_format'=>'replicate'],
'ollama-s204' => ['name'=>'Ollama S204 Sovereign','tier'=>'sovereign','speed'=>'slow',
'base_url'=>'http://127.0.0.1:4000/v1',
'api_key'=>'ollama',
'models'=>['qwen3:8b'=>'Qwen 3 8B','qwen3:4b'=>'Qwen 3 4B','qwen2.5:7b'=>'Qwen 2.5 7B','qwen3.5:0.8b'=>'Qwen 3.5 0.8B Tiny','granite4:latest'=>'IBM Granite 4','mistral:latest'=>'Mistral 7B','glm4:9b'=>'GLM-4 9B Zhipu','medllama2:latest'=>'MedLlama 2 Medical','meditron:7b'=>'Meditron 7B Medical','weval-brain:latest'=>'WEVAL Brain Custom']],
'ollama-s151' => ['name'=>'Ollama S151 OVH','tier'=>'sovereign','speed'=>'slow',
'base_url'=>'http://127.0.0.1:4000/v1',
'api_key'=>'ollama',
'models'=>['qwen3:4b'=>'Qwen 3 4B','qwen3.5:0.8b'=>'Qwen 3.5 0.8B','granite4:latest'=>'IBM Granite 4','phi4-mini:latest'=>'Microsoft Phi-4 Mini','smollm2:135m'=>'SmolLM2 135M Ultra-tiny']],
];
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
header('Content-Type: application/json');
$out = []; $tm = 0;
foreach ($PROVIDERS as $id => $p) {
$models = [];
foreach ($p['models'] as $mid => $mname) { $models[] = ['id'=>$mid,'name'=>$mname]; $tm++; }
$out[] = ['id'=>$id,'name'=>$p['name'],'tier'=>$p['tier'],'speed'=>$p['speed'],'models'=>$models,'has_key'=>!empty($p['api_key'])&&strlen($p['api_key'])>5];
}
echo json_encode(['providers'=>$out,'total_models'=>$tm]);
exit;
}
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) { http_response_code(400); echo json_encode(['error'=>'Invalid JSON']); exit; }
$provider_id = $input['provider'] ?? 'cerebras';
$model = $input['model'] ?? '';
$messages = $input['messages'] ?? [];
$stream = $input['stream'] ?? false;
$system = $input['system'] ?? '';
if (!isset($PROVIDERS[$provider_id])) { http_response_code(400); echo json_encode(['error'=>'Unknown provider']); exit; }
$provider = $PROVIDERS[$provider_id];
if (empty($provider['api_key'])||strlen($provider['api_key'])<5) { http_response_code(400); echo json_encode(['error'=>'No API key for '.$provider['name']]); exit; }
if ($model==="auto"||!$model) $model = array_key_first($provider['models']);
$msgs = $messages;
if (!$system) $system = WEVAL_BRAND_CONTEXT.'Tu es un assistant IA.';
if ($system) array_unshift($msgs, ['role'=>'system','content'=>$system]);
if (($provider['custom_format'] ?? '') === 'anthropic') {
$url = $provider['base_url'].'/messages';
$user_msgs = array_values(array_filter($msgs, fn($m)=>$m['role']!=='system'));
$payload = json_encode(['model'=>$model,'max_tokens'=>intval($input['max_tokens']??4096),'system'=>$system?:'You are a helpful assistant.','messages'=>$user_msgs,'stream'=>$stream]);
$headers = ['Content-Type: application/json','x-api-key: '.$provider['api_key'],'anthropic-version: 2023-06-01'];
} else {
$url = $provider['base_url'].'/chat/completions';
$payload = json_encode(['model'=>$model,'messages'=>$msgs,'stream'=>$stream,'max_tokens'=>intval($input['max_tokens']??4096),'temperature'=>floatval($input['temperature']??0.7)]);
$headers = ['Content-Type: application/json','Authorization: Bearer '.$provider['api_key']];
}
if ($stream) {
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>$payload,CURLOPT_HTTPHEADER=>$headers,CURLOPT_RETURNTRANSFER=>false,CURLOPT_TIMEOUT=>120,CURLOPT_SSL_VERIFYPEER=>false,
CURLOPT_WRITEFUNCTION=>function($ch,$data){echo $data;if(ob_get_level())ob_flush();flush();return strlen($data);}]);
curl_exec($ch); curl_close($ch);
} else {
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>$payload,CURLOPT_HTTPHEADER=>$headers,CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>120,CURLOPT_SSL_VERIFYPEER=>false]);
$resp = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
if (($provider['custom_format']??'')==='anthropic') {
$d = json_decode($resp, true);
if (isset($d['content'][0]['text'])) $resp = json_encode(['choices'=>[['message'=>['role'=>'assistant','content'=>$d['content'][0]['text']]]],'model'=>$d['model']??$model,'usage'=>$d['usage']??[]]);
}
http_response_code($code?:500); echo $resp;
}