303 lines
16 KiB
PHP
303 lines
16 KiB
PHP
<?php
|
|
// WEVIA DeepSeek Proxy v2 — 12+ models FREE via SambaNova + Gemini + Groq
|
|
header("Content-Type: application/json");
|
|
header("Access-Control-Allow-Origin: *");
|
|
header("Access-Control-Allow-Headers: Content-Type");
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') exit;
|
|
|
|
// Token management endpoint
|
|
$raw = json_decode(file_get_contents("php://input"), true) ?: [];
|
|
if (($raw['action'] ?? '') === 'set_ds_token' && !empty($raw['token'])) {
|
|
$token = trim($raw['token']);
|
|
file_put_contents("/etc/weval/deepseek-web-token.txt", $token);
|
|
// Also push to DeepSeek Web service
|
|
$ch = curl_init("http://localhost:8901/set_token");
|
|
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_POST=>true, CURLOPT_POSTFIELDS=>json_encode(["token"=>$token]), CURLOPT_HTTPHEADER=>["Content-Type: application/json"], CURLOPT_TIMEOUT=>5]);
|
|
$r = curl_exec($ch);
|
|
echo json_encode(["status"=>"token_saved","length"=>strlen($token),"ds_web"=>$r?:"ok"]);
|
|
exit;
|
|
}
|
|
if (($raw['action'] ?? '') === 'ds_token_status') {
|
|
$has = file_exists("/etc/weval/deepseek-web-token.txt") && strlen(trim(@file_get_contents("/etc/weval/deepseek-web-token.txt"))) > 10;
|
|
echo json_encode(["has_token"=>$has]);
|
|
exit;
|
|
}
|
|
|
|
$input = json_decode(file_get_contents("php://input"), true);
|
|
$message = $input['message'] ?? '';
|
|
$mode = $input['mode'] ?? 'instant';
|
|
$model = $input['model'] ?? 'auto';
|
|
$history = $input['history'] ?? [];
|
|
|
|
// OPUS 20avr doctrine #2 ZERO simulation: check structured intents BEFORE LLM fallback
|
|
// Prevents hallucination on diagnostic/vault/self-reference questions
|
|
if (!empty($message) && file_exists(__DIR__ . "/wevia-self-diagnostic-intent.php")) {
|
|
require_once __DIR__ . "/wevia-self-diagnostic-intent.php";
|
|
if (function_exists("wevia_self_diagnostic")) {
|
|
$_sd_result = wevia_self_diagnostic($message);
|
|
if ($_sd_result) {
|
|
$_sd_content = isset($_sd_result["content"]) ? $_sd_result["content"] : json_encode($_sd_result, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
|
|
echo json_encode([
|
|
"content" => $_sd_content,
|
|
"provider" => "opus46",
|
|
"tool" => "self_diagnostic",
|
|
"model" => "php-intent-real-exec",
|
|
"note" => "Doctrine #2: real shell execution, not LLM simulation"
|
|
], JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
$temp = floatval($input['temperature'] ?? 0.7);
|
|
// === DEEPSEEK WEB DIRECT (port 8901) — FREE UNLIMITED ===
|
|
if ($model === 'deepseek-web' || $model === 'deepseek-web-think' || $model === 'deepseek-web-search') {
|
|
$ds_mode = 'instant';
|
|
if ($model === 'deepseek-web-think' || $mode === 'deepthink') $ds_mode = 'deepthink';
|
|
if ($model === 'deepseek-web-search' || $mode === 'search') $ds_mode = 'search';
|
|
if ($mode === 'deepthink-search') { $ds_mode = 'deepthink'; } // DeepSeek Web: think takes priority, search via SearXNG
|
|
|
|
$ds_payload = json_encode(["message" => $message, "mode" => $ds_mode], JSON_UNESCAPED_UNICODE);
|
|
$ch = curl_init("http://localhost:8901/chat");
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => $ds_payload,
|
|
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
|
|
CURLOPT_TIMEOUT => 5
|
|
]);
|
|
$r = curl_exec($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
if ($code === 200) {
|
|
$d = @json_decode($r, true);
|
|
if (isset($d['content'])) {
|
|
$d['provider'] = 'DeepSeek Web UNLIMITED';
|
|
$d['model'] = 'deepseek-web-' . $ds_mode;
|
|
$d['cost'] = '0€ illimité';
|
|
echo json_encode($d);
|
|
exit;
|
|
}
|
|
}
|
|
// If DeepSeek Web fails (no token), fall through to cascade
|
|
$model = 'auto'; // FALLTHROUGH: use cascade when DS Web fails
|
|
}
|
|
|
|
|
|
// === GEMINI NATIVE DEEPTHINK (with thinking chain) ===
|
|
if (($mode === 'deepthink' || $mode === 'deepthink-search') && $model !== 'deepseek-web-think') {
|
|
$env2 = @file_get_contents("/etc/weval/secrets.env") ?: "";
|
|
$gkey = ""; if (preg_match("/GEMINI_KEY=(.+)/", $env2, $gm)) $gkey = trim($gm[1]);
|
|
if ($gkey) {
|
|
$prompt = $message;
|
|
// If search mode too, enrich with SearXNG
|
|
if ($mode === 'deepthink-search') {
|
|
$enc = urlencode($message);
|
|
$sch = curl_init("http://localhost:8888/search?q=$enc&format=json&categories=general");
|
|
curl_setopt_array($sch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 6]);
|
|
$sr = curl_exec($sch);
|
|
$sd = @json_decode($sr, true);
|
|
$web = [];
|
|
foreach (array_slice($sd["results"] ?? [], 0, 5) as $wr) {
|
|
$web[] = $wr["title"] . ": " . substr($wr["content"] ?? "", 0, 120);
|
|
}
|
|
if ($web) $prompt .= "\n\nResultats web:\n" . implode("\n", $web);
|
|
}
|
|
|
|
$payload = json_encode([
|
|
"contents" => [["parts" => [["text" => $prompt]]]],
|
|
"systemInstruction" => ["parts" => [["text" => "Tu es WEVIA DeepSeek en mode DeepThink. COMMENCE TOUJOURS par une section de reflexion entre balises <think> et </think> ou tu raisonnes etape par etape, analyses les hypotheses, explores les pistes. PUIS donne ta reponse finale APRES la balise </think>. Format: <think>\nRaisonnement...\n</think>\nReponse finale."]]],
|
|
"generationConfig" => [
|
|
"thinkingConfig" => ["thinkingBudget" => 2048],
|
|
"maxOutputTokens" => 4000
|
|
]
|
|
], JSON_UNESCAPED_UNICODE);
|
|
|
|
$t0 = microtime(true);
|
|
$ch = curl_init("https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=$gkey");
|
|
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_TIMEOUT => 45, CURLOPT_SSL_VERIFYPEER => false]);
|
|
$r = curl_exec($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
if ($code === 200) {
|
|
$d = @json_decode($r, true);
|
|
$parts = $d["candidates"][0]["content"]["parts"] ?? [];
|
|
$thinking = ""; $answer = "";
|
|
$full = "";
|
|
foreach ($parts as $p) {
|
|
if (isset($p["text"])) $full .= $p["text"];
|
|
}
|
|
// Parse <think>...</think> tags
|
|
if (preg_match('/<think>(.*?)<\/think>/s', $full, $tm)) {
|
|
$thinking = trim($tm[1]);
|
|
$answer = trim(preg_replace('/<think>.*?<\/think>/s', '', $full));
|
|
} else {
|
|
// Fallback: try to split on </think> even without matching regex
|
|
if (strpos($full, '</think>') !== false) {
|
|
$split = explode('</think>', $full, 2);
|
|
$thinking = trim(str_replace('<think>', '', $split[0]));
|
|
$answer = trim($split[1] ?? '');
|
|
} elseif (strpos($full, '<think>') !== false) {
|
|
// <think> exists but no closing tag - treat first 40% as thinking
|
|
$thinking = trim(str_replace('<think>', '', substr($full, 0, intval(strlen($full)*0.4))));
|
|
$answer = trim(substr($full, intval(strlen($full)*0.4)));
|
|
} else {
|
|
$answer = $full;
|
|
}
|
|
}
|
|
$elapsed = round((microtime(true) - $t0) * 1000);
|
|
$usage = $d["usageMetadata"] ?? [];
|
|
echo json_encode([
|
|
"content" => $answer ?: ($thinking ?: "No response"),
|
|
"thinking" => $thinking,
|
|
"provider" => "Gemini 2.5 Flash DeepThink",
|
|
"model" => "gemini-2.5-flash-thinking",
|
|
"mode" => $mode,
|
|
"tokens" => ($usage["totalTokenCount"] ?? 0),
|
|
"latency_ms" => $elapsed,
|
|
"cost" => "0EUR",
|
|
"has_thinking" => strlen($thinking) > 0
|
|
]);
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
$max_tokens = intval($input['max_tokens'] ?? 2000);
|
|
|
|
if (!$message) { echo json_encode(["error" => "no message"]); exit; }
|
|
|
|
$env = @file_get_contents("/etc/weval/secrets.env") ?: "";
|
|
|
|
// System prompts by mode
|
|
// OPUS 20avr doctrine #4 HONNETE: anti-hallucination guard preprended to all system prompts
|
|
$__anti_halluc_guard = "REGLES STRICTES DOCTRINE 4 HONNETETE: Tu es un LLM. Tu ne peux PAS executer de commandes shell ni lire de fichiers. Si user demande diagnostic/status/lecture fichier/exec commande: reponds 'Cette requete necessite un intent shell reel. Tape diagnostique toi ou demande a Opus de wire un intent dedie.' N INVENTE JAMAIS outputs commandes PIDs paths MD5 timestamps. Si tu ne connais pas une info factuelle dis-le explicitement. ";
|
|
|
|
$systems = [
|
|
'instant' => "Tu es WEVIA DeepSeek, IA souveraine. Reponds de facon concise et precise.",
|
|
'deepthink-search' => "Tu es WEVIA DeepSeek en mode DeepThink+Search. RAISONNE etape par etape ET utilise les resultats de recherche web pour enrichir ton analyse.",
|
|
'deepthink' => "Tu es WEVIA DeepSeek en mode DeepThink. RAISONNE etape par etape:\n1. Analyse le probleme\n2. Identifie les hypotheses\n3. Explore chaque piste\n4. Synthetise\n5. Conclus\nSois rigoureux et structure.",
|
|
'search' => "Tu es WEVIA DeepSeek avec acces web.",
|
|
'expert' => "Tu es WEVIA DeepSeek Expert. PhD-level. Reponds avec rigueur scientifique, citations, formules si necessaire. Profondeur maximale.",
|
|
'code' => "Tu es WEVCODE, expert code souverain. Code COMPLET, FONCTIONNEL, documente. Python/PHP/JS/SQL/Bash. Tests inclus.",
|
|
'creative' => "Tu es un ecrivain creatif de talent. Style riche, evocateur, immersif. Francais soutenu.",
|
|
];
|
|
|
|
$system = $__anti_halluc_guard . ($systems[$mode] ?? $systems['instant']);
|
|
|
|
// Search mode: enrich with SearXNG
|
|
if ($mode === 'search' || $mode === 'deepthink-search') {
|
|
$enc = urlencode($message);
|
|
$ch = curl_init("http://localhost:8888/search?q=$enc&format=json&categories=general");
|
|
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 8]);
|
|
$sr = curl_exec($ch);
|
|
$sd = @json_decode($sr, true);
|
|
$results = [];
|
|
foreach (array_slice($sd['results'] ?? [], 0, 6) as $r) {
|
|
$results[] = "- " . ($r['title'] ?? '') . ": " . substr($r['content'] ?? '', 0, 150);
|
|
}
|
|
if ($results) {
|
|
$system .= "\n\nRESULTATS WEB:\n" . implode("\n", $results) . "\n\nSynthetise ces resultats dans ta reponse.";
|
|
}
|
|
}
|
|
|
|
// Model routing
|
|
$samba_key = ""; $gemini_key = ""; $groq_key = ""; $cerebras_key = "";
|
|
if (preg_match("/SAMBANOVA_KEY=(.+)/", $env, $m)) $samba_key = trim($m[1]);
|
|
if (preg_match("/GEMINI_KEY=(.+)/", $env, $m)) $gemini_key = trim($m[1]);
|
|
if (preg_match("/GROQ_KEY=(.+)/", $env, $m)) $groq_key = trim($m[1]);
|
|
if (preg_match("/CEREBRAS_API_KEY=(.+)/", $env, $m)) $cerebras_key = trim($m[1]);
|
|
|
|
// Auto model selection based on mode
|
|
$model_map = [
|
|
'auto' => [
|
|
'instant' => ['DeepSeek-V3.2', 'samba'],
|
|
'deepthink-search' => "Tu es WEVIA DeepSeek en mode DeepThink+Search. RAISONNE etape par etape ET utilise les resultats de recherche web pour enrichir ton analyse.",
|
|
'deepthink' => ['DeepSeek-R1-0528', 'samba'],
|
|
'search' => ['DeepSeek-V3.2', 'samba'],
|
|
'expert' => ['gemini-2.5-pro', 'gemini'],
|
|
'code' => ['DeepSeek-V3.2', 'samba'],
|
|
'creative' => ['Meta-Llama-3.3-70B-Instruct', 'samba'],
|
|
],
|
|
];
|
|
|
|
// Build provider list based on model selection
|
|
$providers = [];
|
|
if ($model === 'auto') {
|
|
$auto = $model_map['auto'][$mode] ?? ['DeepSeek-V3.2', 'samba'];
|
|
// Primary based on mode
|
|
if ($auto[1] === 'samba' && $samba_key) {
|
|
$providers[] = ["url" => "https://api.sambanova.ai/v1/chat/completions", "key" => $samba_key, "model" => $auto[0], "name" => "SambaNova " . $auto[0]];
|
|
}
|
|
if ($auto[1] === 'gemini' && $gemini_key) {
|
|
$providers[] = ["url" => "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", "key" => $gemini_key, "model" => $auto[0], "name" => "Google " . $auto[0]];
|
|
}
|
|
// Fallbacks
|
|
if ($samba_key) $providers[] = ["url" => "https://api.sambanova.ai/v1/chat/completions", "key" => $samba_key, "model" => "DeepSeek-V3.2", "name" => "SambaNova DeepSeek-V3.2"];
|
|
if ($gemini_key) $providers[] = ["url" => "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", "key" => $gemini_key, "model" => "gemini-2.5-flash", "name" => "Gemini 2.5 Flash"];
|
|
if ($groq_key) $providers[] = ["url" => "https://api.groq.com/openai/v1/chat/completions", "key" => $groq_key, "model" => "llama-3.3-70b-versatile", "name" => "Groq Llama-3.3-70B"];
|
|
if ($cerebras_key) $providers[] = ["url" => "https://api.cerebras.ai/v1/chat/completions", "key" => $cerebras_key, "model" => "llama3.1-8b", "name" => "Cerebras Llama-8B"];
|
|
} else {
|
|
// Specific model selected
|
|
$specific_models = [
|
|
'deepseek-r1' => ["url" => "https://api.sambanova.ai/v1/chat/completions", "key" => $samba_key, "model" => "DeepSeek-R1-0528"],
|
|
'deepseek-v3.2' => ["url" => "https://api.sambanova.ai/v1/chat/completions", "key" => $samba_key, "model" => "DeepSeek-V3.2"],
|
|
'deepseek-v3.1' => ["url" => "https://api.sambanova.ai/v1/chat/completions", "key" => $samba_key, "model" => "DeepSeek-V3.1"],
|
|
'llama-4' => ["url" => "https://api.sambanova.ai/v1/chat/completions", "key" => $samba_key, "model" => "Llama-4-Maverick-17B-128E-Instruct"],
|
|
'gpt-oss-120b' => ["url" => "https://api.sambanova.ai/v1/chat/completions", "key" => $samba_key, "model" => "gpt-oss-120b"],
|
|
'minimax' => ["url" => "https://api.sambanova.ai/v1/chat/completions", "key" => $samba_key, "model" => "MiniMax-M2.5"],
|
|
'gemma-3' => ["url" => "https://api.sambanova.ai/v1/chat/completions", "key" => $samba_key, "model" => "gemma-3-12b-it"],
|
|
'gemini-2.5-pro' => ["url" => "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", "key" => $gemini_key, "model" => "gemini-2.5-pro"],
|
|
'gemini-2.5-flash' => ["url" => "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", "key" => $gemini_key, "model" => "gemini-2.5-flash"],
|
|
'groq-llama' => ["url" => "https://api.groq.com/openai/v1/chat/completions", "key" => $groq_key, "model" => "llama-3.3-70b-versatile"],
|
|
];
|
|
if (isset($specific_models[$model])) {
|
|
$p = $specific_models[$model];
|
|
$p["name"] = ucfirst(str_replace('-', ' ', $model));
|
|
$providers[] = $p;
|
|
}
|
|
}
|
|
|
|
// Adjust max_tokens for DeepThink
|
|
if ($mode === 'deepthink') $max_tokens = max($max_tokens, 4000);
|
|
if ($mode === 'expert') $max_tokens = max($max_tokens, 3000);
|
|
|
|
$messages = [["role" => "system", "content" => $system]];
|
|
foreach (array_slice($history, -12) as $h) {
|
|
$messages[] = ["role" => $h['role'], "content" => $h['content']];
|
|
}
|
|
$messages[] = ["role" => "user", "content" => $message];
|
|
|
|
$t0 = microtime(true);
|
|
// === TIER 0: DeepSeek Web FREE UNLIMITED ===
|
|
require_once __DIR__ . chr(47) . "deepseek-web-bridge.php";
|
|
|
|
foreach ($providers as $p) {
|
|
if (!$p['key']) continue;
|
|
$data = json_encode(["model" => $p["model"], "messages" => $messages, "max_tokens" => $max_tokens, "temperature" => $temp], JSON_UNESCAPED_UNICODE);
|
|
$ch = curl_init($p["url"]);
|
|
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $data, CURLOPT_HTTPHEADER => ["Content-Type: application/json", "Authorization: Bearer " . $p["key"]], CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => false]);
|
|
$r = curl_exec($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
if ($code === 200) {
|
|
$d = @json_decode($r, true);
|
|
$content = $d["choices"][0]["message"]["content"] ?? null;
|
|
$usage = $d["usage"] ?? [];
|
|
if ($content) {
|
|
$elapsed = round((microtime(true) - $t0) * 1000);
|
|
echo json_encode([
|
|
"content" => $content,
|
|
"provider" => $p["name"],
|
|
"model" => $p["model"],
|
|
"mode" => $mode,
|
|
"tokens" => ($usage["total_tokens"] ?? 0),
|
|
"prompt_tokens" => ($usage["prompt_tokens"] ?? 0),
|
|
"completion_tokens" => ($usage["completion_tokens"] ?? 0),
|
|
"latency_ms" => $elapsed,
|
|
"cost" => "0€"
|
|
]);
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
echo json_encode(["error" => "All providers failed", "content" => "Providers indisponibles. Reessayez."]);
|