From a48c05a3f6c22385a59582eefd67a753cba4a6bc Mon Sep 17 00:00:00 2001 From: opus Date: Fri, 17 Apr 2026 17:52:49 +0200 Subject: [PATCH] 17avr-arena-sovereign-fix-6sigma --- api/opus5-autonomous-orchestrator-v3.php | 140 ++++++++++++++ api/opus5-gpu-grid.php | 223 ++++++++++++----------- api/wevia-master-api.php | 3 + api/wevia-nl-normalizer-prehook.php | 52 ++++++ 4 files changed, 316 insertions(+), 102 deletions(-) create mode 100644 api/opus5-autonomous-orchestrator-v3.php create mode 100644 api/wevia-nl-normalizer-prehook.php diff --git a/api/opus5-autonomous-orchestrator-v3.php b/api/opus5-autonomous-orchestrator-v3.php new file mode 100644 index 000000000..fc654b0a9 --- /dev/null +++ b/api/opus5-autonomous-orchestrator-v3.php @@ -0,0 +1,140 @@ +date('c'), 'source'=>'opus5-autonomous-orchestrator-v3']; + +$raw = file_get_contents('php://input'); +$d = json_decode($raw, true) ?: []; +$msg = (string)($d['message'] ?? ''); +$session = (string)($d['session'] ?? 'auto-' . bin2hex(random_bytes(4))); +if (!$msg) { http_response_code(400); echo json_encode(['err'=>'missing_message']); exit; } + +// === STEP 1 : Cache prédictif FIRST (0-20ms) === +$t_cache = microtime(true); +$ch = curl_init('http://127.0.0.1/api/opus5-predictive-cache.php?action=get&key=' . urlencode(substr($msg, 0, 200))); +curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>2]); +$cache_resp = curl_exec($ch); +curl_close($ch); +$cached = @json_decode((string)$cache_resp, true); +if ($cached && ($cached['cache'] ?? '') === 'HIT' && !empty($cached['value']['response'])) { + $R['cache'] = 'HIT'; + $R['final_response'] = $cached['value']['response']; + $R['final_provider'] = $cached['value']['provider'] ?? 'cached'; + $R['total_ms'] = round((microtime(true) - $t0) * 1000); + $R['doctrine'] = '78 — meta-orch v3 cache HIT (instant)'; + echo json_encode($R, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE); + exit; +} +$R['cache'] = 'MISS'; +$R['cache_check_ms'] = round((microtime(true) - $t_cache) * 1000); + +// === STEP 2 : Classification rapide === +$words = str_word_count($msg); +$ml = strtolower($msg); +$has_multi = preg_match('/\b(puis|ensuite|apres|parallele)\b/u', $ml); +$has_analysis = preg_match('/\b(analys|explique|pourquoi|detail|approfond)\b/u', $ml); +$complexity = 'simple'; +if ($has_multi) $complexity = 'multi-step'; +elseif ($has_analysis || $words > 20) $complexity = 'deep'; +$R['classification'] = ['words'=>$words, 'complexity'=>$complexity]; + +// === STEP 3 : Async background (fire-and-forget) === +$async_launched = []; +$logf = "/var/log/weval/orch-v3-$session.log"; +if ($complexity === 'multi-step') { + @shell_exec("timeout 30 /opt/weval-ops/top-ia/dialectical.sh " . escapeshellarg(substr($msg,0,500)) . " >> $logf 2>&1 &"); + $async_launched[] = 'dialectical'; +} +if ($complexity === 'deep') { + @shell_exec("timeout 30 /opt/weval-ops/top-ia/cot_tree.sh " . escapeshellarg(substr($msg,0,500)) . " >> $logf 2>&1 &"); + $async_launched[] = 'cot_tree'; +} +@shell_exec("timeout 15 /opt/weval-ops/top-ia/memory_store.sh " . escapeshellarg($session) . " " . escapeshellarg(substr($msg,0,200)) . " >> $logf 2>&1 &"); +$async_launched[] = 'memory_store'; +$R['async_launched'] = $async_launched; + +// === STEP 4 : Grid parallel via curl_multi (fastest wins) === +$t_grid = microtime(true); +$PROVIDERS = [ + ['id'=>'safe_wrapper', 'url'=>'http://127.0.0.1/api/opus5-weval-ia-fast-safe.php'], + ['id'=>'dispatch_proxy', 'url'=>'http://127.0.0.1/api/wevia-master-dispatch.php'] +]; + +$mh = curl_multi_init(); +$handles = []; +$payload = json_encode(['message'=>$msg, 'session'=>$session]); +foreach ($PROVIDERS as $p) { + $ch = curl_init($p['url']); + curl_setopt_array($ch, [ + CURLOPT_POST=>true, + CURLOPT_POSTFIELDS=>$payload, + CURLOPT_HTTPHEADER=>['Content-Type: application/json'], + CURLOPT_RETURNTRANSFER=>true, + CURLOPT_TIMEOUT=>15, + CURLOPT_CONNECTTIMEOUT=>2 + ]); + curl_multi_add_handle($mh, $ch); + $handles[] = ['ch'=>$ch, 'provider'=>$p, 'started'=>microtime(true)]; +} + +$running = null; +do { curl_multi_exec($mh, $running); curl_multi_select($mh, 0.1); } while ($running > 0); + +$grid_results = []; +$winner = null; +foreach ($handles as $h) { + $body = curl_multi_getcontent($h['ch']); + $http = curl_getinfo($h['ch'], CURLINFO_HTTP_CODE); + $dp = @json_decode((string)$body, true); + $resp_text = $dp['response'] ?? $dp['content'] ?? $dp['final_response'] ?? ''; + $grid_results[] = [ + 'id' => $h['provider']['id'], + 'http' => $http, + 'ms' => round((microtime(true) - $h['started']) * 1000), + 'len' => strlen((string)$resp_text), + 'provider_engine' => $dp['provider'] ?? '?' + ]; + if (!$winner && $http === 200 && strlen((string)$resp_text) > 10) { + $winner = [ + 'provider' => $h['provider']['id'], + 'engine' => $dp['provider'] ?? '?', + 'response' => $resp_text, + 'ms' => round((microtime(true) - $h['started']) * 1000) + ]; + } + curl_multi_remove_handle($mh, $h['ch']); + curl_close($h['ch']); +} +curl_multi_close($mh); + +$R['grid_results'] = $grid_results; +$R['grid_ms'] = round((microtime(true) - $t_grid) * 1000); + +// === STEP 5 : Store dans cache prédictif pour futur (async) === +if ($winner) { + $R['final_response'] = $winner['response']; + $R['final_provider'] = $winner['engine']; + $R['winner'] = $winner['provider']; + + // Store in predictive cache + $cache_payload = json_encode(['response'=>$winner['response'], 'provider'=>$winner['engine']]); + $ch = curl_init('http://127.0.0.1/api/opus5-predictive-cache.php?action=set&key=' . urlencode(substr($msg, 0, 200)) . '&ttl=600'); + curl_setopt_array($ch, [CURLOPT_POST=>true, CURLOPT_POSTFIELDS=>$cache_payload, CURLOPT_HTTPHEADER=>['Content-Type: application/json'], CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>2]); + curl_exec($ch); + curl_close($ch); + $R['cached_for_future'] = true; +} else { + $R['final_response'] = '[orchestrator v3] Tous les providers ont timeout ou retourné vide.'; + $R['final_provider'] = 'none'; +} + +$R['total_ms'] = round((microtime(true) - $t0) * 1000); +$R['session'] = $session; +$R['doctrine'] = '78 — meta-orch v3 (cache-first + curl_multi parallel + cache-after)'; + +echo json_encode($R, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE); diff --git a/api/opus5-gpu-grid.php b/api/opus5-gpu-grid.php index d171d73b8..234509d84 100644 --- a/api/opus5-gpu-grid.php +++ b/api/opus5-gpu-grid.php @@ -1,162 +1,181 @@ date('c'), 'source'=>'opus5-gpu-grid']; +$R = ['ts'=>date('c'), 'source'=>'opus5-gpu-grid-v2']; $raw = file_get_contents('php://input'); $d = json_decode($raw, true) ?: []; $action = $_GET['action'] ?? ($d['action'] ?? 'health'); -// PROVIDERS — configuration $PROVIDERS = [ [ - 'id' => 'ollama_local', - 'url' => 'http://127.0.0.1:11434/api/generate', - 'model' => 'qwen3:4b', - 'type' => 'ollama', - 'header' => ['Content-Type: application/json'], - 'weight' => 1 + 'id' => 'safe_wrapper', + 'url' => 'http://127.0.0.1/api/opus5-weval-ia-fast-safe.php', + 'type' => 'internal' ], [ - 'id' => 'sovereign_cascade', - 'url' => 'http://127.0.0.1/api/opus5-weval-ia-fast-safe.php', - 'type' => 'internal', - 'header' => ['Content-Type: application/json'], - 'weight' => 1 + 'id' => 'meta_orchestrator', + 'url' => 'http://127.0.0.1/api/opus5-autonomous-orchestrator.php', + 'type' => 'internal_orch' ], + [ + 'id' => 'dispatch_proxy', + 'url' => 'http://127.0.0.1/api/wevia-master-dispatch.php', + 'type' => 'internal_disp' + ] ]; -function call_provider($p, $prompt, $max_tokens = 200) { - if ($p['type'] === 'ollama') { - $payload = json_encode(['model'=>$p['model'], 'prompt'=>$prompt, 'stream'=>false, 'options'=>['num_predict'=>$max_tokens]]); - } elseif ($p['type'] === 'internal') { - $payload = json_encode(['message'=>$prompt]); - } else { - $payload = json_encode(['prompt'=>$prompt]); - } - $ch = curl_init($p['url']); +function build_ch($url, $payload, $timeout = 20) { + $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, - CURLOPT_HTTPHEADER => $p['header'], + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_RETURNTRANSFER => true, - CURLOPT_TIMEOUT => 25, + CURLOPT_TIMEOUT => $timeout, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => 0 ]); return $ch; } -function parse_provider_response($p, $body) { +function parse_response($body) { $d = @json_decode((string)$body, true); - if ($p['type'] === 'ollama') return $d['response'] ?? ''; - if ($p['type'] === 'internal') return $d['response'] ?? $d['content'] ?? ''; - return is_string($body) ? substr($body, 0, 500) : ''; + if (!$d) return ['text' => substr((string)$body, 0, 500), 'provider' => '?']; + return [ + 'text' => $d['response'] ?? $d['content'] ?? $d['final_response'] ?? substr((string)$body, 0, 300), + 'provider' => $d['provider'] ?? $d['final_provider'] ?? '?' + ]; } if ($action === 'health') { - // Quick health check each provider - $results = []; + $mh = curl_multi_init(); + $handles = []; + $t0 = microtime(true); foreach ($PROVIDERS as $p) { - $t0 = microtime(true); - if ($p['type'] === 'ollama') { - $ch = curl_init(str_replace('/api/generate', '/api/tags', $p['url'])); - curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>3]); - curl_exec($ch); - $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - } else { - $ch = curl_init($p['url']); - curl_setopt_array($ch, [CURLOPT_POST=>true, CURLOPT_POSTFIELDS=>'{"message":"ping"}', CURLOPT_HTTPHEADER=>$p['header'], CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>3, CURLOPT_SSL_VERIFYPEER=>false]); - curl_exec($ch); - $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - } - $results[] = [ - 'id' => $p['id'], - 'url' => $p['url'], - 'up' => $http === 200, - 'ms' => round((microtime(true) - $t0) * 1000) - ]; + $ch = build_ch($p['url'], json_encode(['message'=>'ping']), 5); + curl_multi_add_handle($mh, $ch); + $handles[] = ['ch'=>$ch, 'provider'=>$p]; } + $running = null; + do { curl_multi_exec($mh, $running); curl_multi_select($mh, 0.1); } while ($running > 0); + + $results = []; + foreach ($handles as $h) { + $http = curl_getinfo($h['ch'], CURLINFO_HTTP_CODE); + $results[] = ['id'=>$h['provider']['id'], 'up'=>$http===200, 'http'=>$http]; + curl_multi_remove_handle($mh, $h['ch']); + curl_close($h['ch']); + } + curl_multi_close($mh); $R['providers'] = $results; - $R['up_count'] = count(array_filter($results, function($r){ return $r['up']; })); - $R['total_count'] = count($results); + $R['up_count'] = count(array_filter($results, function($r){return $r['up'];})); + $R['parallel_ms'] = round((microtime(true)-$t0)*1000); echo json_encode($R, JSON_PRETTY_PRINT); exit; } -if ($action === 'shard') { - // Shard un gros prompt en N morceaux + dispatch parallel via curl_multi - $text = (string)($d['text'] ?? ''); - $instruction = (string)($d['instruction'] ?? 'Résume ce passage en 2 phrases'); - if (!$text) { http_response_code(400); echo json_encode(['err'=>'no_text']); exit; } +if ($action === 'parallel_query') { + // Send same query to all providers in parallel, return first valid response + all for comparison + $query = (string)($d['query'] ?? ''); + if (!$query) { http_response_code(400); echo json_encode(['err'=>'no_query']); exit; } - // Chunking simple : split par ~500 chars - $chunks = []; - $chunk_size = 500; - for ($i = 0; $i < strlen($text); $i += $chunk_size) { - $chunks[] = substr($text, $i, $chunk_size); - } - $R['chunks_count'] = count($chunks); - if (count($chunks) > 10) { $chunks = array_slice($chunks, 0, 10); } // cap 10 shards - - // Dispatch round-robin across providers - $t0 = microtime(true); $mh = curl_multi_init(); $handles = []; - foreach ($chunks as $idx => $chunk) { - $p = $PROVIDERS[$idx % count($PROVIDERS)]; - $full_prompt = $instruction . "\n\nPassage:\n" . $chunk . "\n\nRéponse:"; - $ch = call_provider($p, $full_prompt); + $t0 = microtime(true); + foreach ($PROVIDERS as $p) { + $ch = build_ch($p['url'], json_encode(['message'=>$query]), 25); curl_multi_add_handle($mh, $ch); - $handles[] = ['ch'=>$ch, 'provider'=>$p, 'chunk_idx'=>$idx]; + $handles[] = ['ch'=>$ch, 'provider'=>$p, 'started'=>microtime(true)]; } - - // Run parallel $running = null; - do { - curl_multi_exec($mh, $running); - curl_multi_select($mh, 0.1); - } while ($running > 0); + do { curl_multi_exec($mh, $running); curl_multi_select($mh, 0.1); } while ($running > 0); - // Collect results - $shard_results = []; + $results = []; foreach ($handles as $h) { $body = curl_multi_getcontent($h['ch']); $http = curl_getinfo($h['ch'], CURLINFO_HTTP_CODE); - $text_response = parse_provider_response($h['provider'], $body); - $shard_results[] = [ - 'chunk_idx' => $h['chunk_idx'], - 'provider' => $h['provider']['id'], + $parsed = parse_response($body); + $results[] = [ + 'provider_id' => $h['provider']['id'], 'http' => $http, - 'response_preview' => substr($text_response, 0, 300), - 'response_len' => strlen($text_response) + 'ms' => round((microtime(true) - $h['started']) * 1000), + 'response_preview' => substr($parsed['text'] ?? '', 0, 300), + 'response_len' => strlen($parsed['text'] ?? ''), + 'engine' => $parsed['provider'] ?? '?' ]; curl_multi_remove_handle($mh, $h['ch']); curl_close($h['ch']); } curl_multi_close($mh); - $R['shards'] = $shard_results; - $R['total_ms'] = round((microtime(true) - $t0) * 1000); - $R['providers_used'] = array_values(array_unique(array_column($shard_results, 'provider'))); + // Pick winner: fastest with non-empty response + $valid = array_filter($results, function($r){ return $r['http'] === 200 && $r['response_len'] > 10; }); + usort($valid, function($a,$b){ return $a['ms'] - $b['ms']; }); + $R['winner'] = !empty($valid) ? $valid[0] : null; + $R['all_results'] = $results; + $R['total_wall_ms'] = round((microtime(true) - $t0) * 1000); + $R['speedup'] = count($results) > 0 ? round(array_sum(array_column($results, 'ms')) / $R['total_wall_ms'], 1) . 'x' : 'N/A'; + $R['doctrine'] = '77 — grid GPU parallel (curl_multi multi-endpoint + fastest-wins)'; - // Merge : concaténer responses dans ordre chunk_idx - usort($shard_results, function($a,$b){ return $a['chunk_idx'] - $b['chunk_idx']; }); - $merged = []; - foreach ($shard_results as $s) { - if ($s['response_len'] > 10) $merged[] = $s['response_preview']; + echo json_encode($R, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE); + exit; +} + +if ($action === 'shard_text') { + // Split large text into chunks, process each on different provider in parallel + $text = (string)($d['text'] ?? ''); + $instruction = (string)($d['instruction'] ?? 'Résume'); + if (!$text) { http_response_code(400); echo json_encode(['err'=>'no_text']); exit; } + + $chunk_size = 600; + $chunks = []; + for ($i = 0; $i < strlen($text); $i += $chunk_size) { + $chunks[] = substr($text, $i, $chunk_size); + if (count($chunks) >= 6) break; // cap 6 shards } - $R['merged_preview'] = substr(implode("\n---\n", $merged), 0, 1500); - $R['doctrine'] = '77 — grid GPU distribué (curl_multi shard round-robin multi-provider)'; + + $t0 = microtime(true); + $mh = curl_multi_init(); + $handles = []; + foreach ($chunks as $idx => $chunk) { + $p = $PROVIDERS[$idx % count($PROVIDERS)]; + $full = "$instruction\n\n$chunk"; + $ch = build_ch($p['url'], json_encode(['message'=>$full]), 20); + curl_multi_add_handle($mh, $ch); + $handles[] = ['ch'=>$ch, 'provider'=>$p, 'idx'=>$idx]; + } + $running = null; + do { curl_multi_exec($mh, $running); curl_multi_select($mh, 0.1); } while ($running > 0); + + $shards = []; + foreach ($handles as $h) { + $body = curl_multi_getcontent($h['ch']); + $parsed = parse_response($body); + $shards[] = [ + 'idx' => $h['idx'], + 'provider' => $h['provider']['id'], + 'response_preview' => substr($parsed['text'] ?? '', 0, 200), + 'response_len' => strlen($parsed['text'] ?? '') + ]; + curl_multi_remove_handle($mh, $h['ch']); + curl_close($h['ch']); + } + curl_multi_close($mh); + + usort($shards, function($a,$b){ return $a['idx'] - $b['idx']; }); + + $R['chunks_count'] = count($chunks); + $R['shards'] = $shards; + $R['total_ms'] = round((microtime(true) - $t0) * 1000); + $R['merged'] = implode("\n---\n", array_column($shards, 'response_preview')); + $R['providers_used'] = array_values(array_unique(array_column($shards, 'provider'))); echo json_encode($R, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE); exit; } http_response_code(400); -echo json_encode(['err'=>'unknown_action', 'available'=>['health','shard']]); +echo json_encode(['err'=>'unknown_action', 'available'=>['health','parallel_query','shard_text']]); diff --git a/api/wevia-master-api.php b/api/wevia-master-api.php index 123f3e031..e198370a5 100644 --- a/api/wevia-master-api.php +++ b/api/wevia-master-api.php @@ -295,6 +295,9 @@ if (!empty($_mam)) { // === END OPUS_ROOT_CAUSE_GUARDS_EARLY_17AVR === +// V26-SURGICAL normalizer prehook (Opus 17avr 17h50) — fix "combien j'ai de leads" → route correct +@require_once __DIR__ . "/wevia-nl-normalizer-prehook.php"; + // === OPUS_BUSINESS_COUNT_GUARD_17AVR (natural language → SQL real) === if (!empty($_mam)) { $__bm = $_mam; diff --git a/api/wevia-nl-normalizer-prehook.php b/api/wevia-nl-normalizer-prehook.php new file mode 100644 index 000000000..5024b773e --- /dev/null +++ b/api/wevia-nl-normalizer-prehook.php @@ -0,0 +1,52 @@ + '$1 ', + // "j'ai combien de X" → "combien X" + '/\bj[\' ]?ai\s+(combien|nombre|total)\s+(?:de\s+|des\s+|du\s+|d[\' ])?\b/iu' => '$1 ', + // "mes/mon/ma X" entre combien et entité + '/\b(combien|nombre|total|quantit[eé])\s+(?:de\s+|des\s+)?(?:mes|mon|ma|mon\s+pipeline|mes\s+actuels?)\s+\b/iu' => '$1 ', + // Articles résiduels + '/\b(combien|nombre|total)\s+(?:de\s+|des\s+|du\s+|d[\' ])\s+\b/iu' => '$1 ', + ]; + + $__normalized = $__orig; + foreach ($__patterns as $__pat => $__rep) { + $__normalized = preg_replace($__pat, $__rep, $__normalized); + } + + // Si normalisation a changé quelque chose, update $_mam pour le bloc suivant + if ($__normalized !== $__orig) { + // Log pour audit trail + @file_put_contents( + '/var/log/weval/wevia-nl-normalizer.log', + date('c') . " NORMALIZED: '$__orig' → '$__normalized'\n", + FILE_APPEND | LOCK_EX + ); + $_mam = $__normalized; + // $_wm aussi (shared global) + if (isset($_wm)) $_wm = $__normalized; + } +}