17avr-arena-sovereign-fix-6sigma
This commit is contained in:
140
api/opus5-autonomous-orchestrator-v3.php
Normal file
140
api/opus5-autonomous-orchestrator-v3.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
// OPUS5 — Meta-Orchestrator v3 (doctrine 78)
|
||||
// v2 faisait async heavy + sync critical. v3 ajoute :
|
||||
// - curl_multi pour appeler 3 providers en PARALLÈLE + fastest-wins
|
||||
// - Cache prédictif checked FIRST (hit = instant return)
|
||||
// - Fallback chain intelligent si tous providers timeout
|
||||
header('Content-Type: application/json');
|
||||
$t0 = microtime(true);
|
||||
$R = ['ts'=>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);
|
||||
@@ -1,162 +1,181 @@
|
||||
<?php
|
||||
// OPUS5 — Grid GPU distribué (doctrine 77)
|
||||
// Shard un gros prompt sur plusieurs providers gratuits en parallèle via curl_multi
|
||||
// Providers: Ollama local (toujours UP), puis Groq/Cerebras/SambaNova/HF Inference (si keys)
|
||||
// Mode adaptatif : utilise seulement providers UP
|
||||
// OPUS5 — Grid GPU v2 (doctrine 77) — MULTI-ENDPOINT PARALLEL via curl_multi
|
||||
// Utilise les 3 endpoints LLM internes existants en parallèle pour ~3x throughput
|
||||
// Endpoints: sovereign-proxy (SSE), weval-ia-fast-safe, opus5-autonomous-orchestrator
|
||||
header('Content-Type: application/json');
|
||||
$R = ['ts'=>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']]);
|
||||
|
||||
@@ -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;
|
||||
|
||||
52
api/wevia-nl-normalizer-prehook.php
Normal file
52
api/wevia-nl-normalizer-prehook.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* WEVIA NL NORMALIZER — V26-SURGICAL · Opus · 17 avril 2026 17h50
|
||||
*
|
||||
* Doctrine 72 (ex-71) respectée : fix chirurgical 1 ligne ajoutée dans master-api.php (pas modification)
|
||||
* Doctrine 63 : non destructif, fichier séparé loadé avant OPUS_BUSINESS_COUNT_GUARD
|
||||
*
|
||||
* Fix : "combien j'ai de leads" avec apostrophe/espace → normalise vers "combien leads"
|
||||
*
|
||||
* Problème originel (ligne 322 master-api.php, auteur Opus5) :
|
||||
* preg_match('/\b(?:combien|nombre)\s+(?:de\s+|d\'|of\s+)?([a-z_]+)/iu', ...)
|
||||
* capture le premier token après "combien" → "j'ai" au lieu de "leads"
|
||||
*
|
||||
* Solution NL normalizer : normalise $_mam (message) en supprimant les intercalaires
|
||||
* "j'ai/jai/j ai/nous avons/tu as/mon/mes/de mes..." → ""
|
||||
*
|
||||
* Tests : "combien j'ai de leads" → "combien leads" → route count_business_real OK
|
||||
*/
|
||||
|
||||
if (!empty($_mam) && is_string($_mam)) {
|
||||
$__orig = $_mam;
|
||||
|
||||
// Patterns à normaliser (ordre important: plus spécifique d'abord)
|
||||
$__patterns = [
|
||||
// Possessifs + auxiliaires entre "combien" et entité
|
||||
'/\b(combien|nombre|count|total|quantit[eé])\s+(?:j[\' ]?ai|jai|nous\s+avons|tu\s+as|on\s+a|il\s+y\s+a|vous\s+avez)\s+(?:de\s+|des\s+|du\s+|d[\' ]\s*)?\b/iu' => '$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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user