Files
html/api/l99-chat.php
2026-04-23 22:40:03 +02:00

117 lines
4.4 KiB
PHP

<?php
require_once __DIR__.'/weval-brand-guard.php';
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');
header('Access-Control-Allow-Origin: *');
if(ob_get_level())ob_end_clean();
$token=$_GET['token']??'';
if($token!=='L99CHAT2026'){echo "data: ".json_encode(['t'=>'Auth required'])."
data: [DONE]
";exit;}
$q=$_GET['q']??'';
if(!$q){echo "data: ".json_encode(['t'=>'No query'])."
data: [DONE]
";exit;}
// DOCTRINE-146-SSE-START
$__l99_accum = '';
$__l99_qmsg = $q;
register_shutdown_function(function() use (&$__l99_accum, $__l99_qmsg) {
if (!$__l99_qmsg || strlen($__l99_accum) < 1) return;
$key = 'chatmem:l99-chat:' . date('Ymd_His') . '_' . substr(md5(uniqid('',1)),0,6);
$payload = json_encode([
'chatbot' => 'l99-chat',
'ts' => date('c'),
'msg' => $__l99_qmsg,
'response' => $__l99_accum,
'len' => strlen($__l99_accum),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
@shell_exec('redis-cli -n 5 SET ' . escapeshellarg($key) . ' ' . escapeshellarg($payload) . ' EX 604800 > /dev/null 2>&1 &');
});
// DOCTRINE-146-SSE-END
$env=[];
foreach(file('/etc/weval/secrets.env',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) as $l){
if($l[0]==='#'||!strpos($l,'='))continue;
[$k,$v]=explode('=',$l,2);
$env[trim($k)]=trim($v,'" ');
}
$sys="'.WEVAL_BRAND_CONTEXT.'Tu es L99 Brain, l'IA souveraine de WEVAL Consulting. Expert DevOps, Lean Six Sigma, architecture cloud. Reponds en francais, concis et actionnable.";
$msgs=[['role'=>'system','content'=>$sys],['role'=>'user','content'=>$q]];
// Cascade: Groq → Cerebras → Ollama
$providers=[
['n'=>'SambaNova','u'=>'https://api.sambanova.ai/v1/chat/completions','k'=>$env['SAMBANOVA_KEY']??'','m'=>'Llama-4-Maverick-17B-128E-Instruct'],
['n'=>'SambaNova','u'=>'https://api.sambanova.ai/v1/chat/completions','k'=>$env['SAMBANOVA_KEY']??'','m'=>'Meta-Llama-3.3-70B-Instruct'],
['n'=>'Groq','u'=>'https://api.groq.com/openai/v1/chat/completions','k'=>$env['GROQ_KEY']??'','m'=>'moonshotai/kimi-k2-instruct'],
['n'=>'Mistral','u'=>'https://api.mistral.ai/v1/chat/completions','k'=>$env['MISTRAL_KEY']??'','m'=>'mistral-small-latest'],
['n'=>'Cerebras','u'=>'https://api.cerebras.ai/v1/chat/completions','k'=>$env['CEREBRAS_API_KEY']??'','m'=>'qwen-3-235b-a22b-instruct-2507'],
['n'=>'Groq','u'=>'https://api.groq.com/openai/v1/chat/completions','k'=>$env['GROQ_KEY']??'','m'=>'llama-3.3-70b-versatile'],
];
foreach($providers as $p){
if(!$p['k'])continue;
$ch=curl_init($p['u']);
curl_setopt_array($ch,[
CURLOPT_POST=>1,
CURLOPT_POSTFIELDS=>json_encode(['model'=>$p['m'],'messages'=>$msgs,'stream'=>true,'max_tokens'=>1500]),
CURLOPT_HTTPHEADER=>['Content-Type: application/json','Authorization: Bearer '.$p['k']],
CURLOPT_RETURNTRANSFER=>0,CURLOPT_TIMEOUT=>25,
CURLOPT_WRITEFUNCTION=>function($c,$d)use($p,&$__l99_accum){
foreach(explode("
",$d) as $line){
$line=trim($line);
if(!$line||$line==='data: [DONE]')continue;
if(strpos($line,'data: ')===0)$line=substr($line,6);
$j=@json_decode($line,1);
if(!$j)continue;
$t=$j['choices'][0]['delta']['content']??'';
if($t){$__l99_accum.=$t;echo "data: ".json_encode(['t'=>$t,'p'=>$p['n']])."
";@ob_flush();flush();}
}
return strlen($d);
}
]);
$ok=curl_exec($ch);
$code=curl_getinfo($ch,CURLINFO_HTTP_CODE);
curl_close($ch);
if($code>=200&&$code<400){echo "data: [DONE]
";exit;}
}
// Fallback: Ollama local
$ch2=curl_init('http://127.0.0.1:11434/api/chat');
curl_setopt_array($ch2,[
CURLOPT_POST=>1,
CURLOPT_POSTFIELDS=>json_encode(['model'=>'qwen3:8b','messages'=>$msgs,'stream'=>true]),
CURLOPT_HTTPHEADER=>['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER=>0,CURLOPT_TIMEOUT=>30,
CURLOPT_WRITEFUNCTION=>function($c,$d)use(&$__l99_accum){
foreach(explode("
",$d) as $line){
$j=@json_decode(trim($line),1);
if($j&&isset($j['message']['content'])){
$__l99_accum.=$j['message']['content'];
echo "data: ".json_encode(['t'=>$j['message']['content'],'p'=>'Ollama'])."
";
@ob_flush();flush();
}
}
return strlen($d);
}
]);
curl_exec($ch2);curl_close($ch2);
echo "data: [DONE]
";