false, 'error' => 'Missing message or providers']); exit; } $message = $input['message']; $providers = $input['providers']; $whitelist = ['wevia','openai','anthropic','google','deepseek','mistral','poe','perplexity','hf']; $providers = array_values(array_intersect($providers, $whitelist)); if (empty($providers)) { echo json_encode(['ok' => false, 'error' => 'No valid providers']); exit; } $start_total = microtime(true); $responses = []; // === WEVIA Master pivot - always queries KB first if requested === $wevia_kb_context = null; if (in_array('wevia', $providers) || (!empty($input['augment_with_kb']))) { $wevia_kb_context = queryWeviaKB($message); } // === Process each provider === foreach ($providers as $slug) { $r_start = microtime(true); if ($slug === 'wevia') { // WEVIA Master via sovereign-api with KB augment $resp = callWeviaMaster($message, $wevia_kb_context); $responses[] = [ 'slug' => 'wevia', 'name' => 'WEVIA Master', 'ok' => !empty($resp['content']), 'response' => $resp['content'] ?? null, 'error' => $resp['error'] ?? null, 'model' => $resp['model'] ?? null, 'provider_used' => $resp['provider'] ?? null, 'kb_context_chars' => $wevia_kb_context ? strlen($wevia_kb_context) : 0, 'latency_ms' => round((microtime(true) - $r_start) * 1000) ]; } } // === Chrome CDP providers (parallel via Node) === $cdp_providers = array_values(array_diff($providers, ['wevia'])); if (!empty($cdp_providers)) { $reqId = bin2hex(random_bytes(6)); $reqFile = "/tmp/cdp-broadcast-req-$reqId.json"; $resFile = "/tmp/cdp-broadcast-res-$reqId.json"; file_put_contents($reqFile, json_encode([ 'message' => $message, 'providers' => $cdp_providers, 'req_id' => $reqId ])); $nodeScript = '/opt/wevia-brain/scripts/cdp-broadcast.js'; $cmd = "timeout 60 node " . escapeshellarg($nodeScript) . " " . escapeshellarg($reqFile) . " " . escapeshellarg($resFile) . " 2>&1"; shell_exec($cmd); if (file_exists($resFile)) { $cdp_responses = json_decode(file_get_contents($resFile), true) ?: []; foreach ($cdp_responses as $r) { // If CDP fails (e.g. not logged in), fallback to sovereign-api with provider hint if (empty($r['ok']) && !empty($input['fallback_sovereign']) && $input['fallback_sovereign'] !== false) { $fallback = callWeviaMaster( "[As $r[slug] would respond] $message", $wevia_kb_context ); $r['ok'] = !empty($fallback['content']); $r['response'] = $fallback['content'] ?? $r['response']; $r['fallback_used'] = 'sovereign:' . ($fallback['provider'] ?? '?'); $r['original_error'] = $r['error']; $r['error'] = null; } $responses[] = $r; } @unlink($resFile); } @unlink($reqFile); } $ok_count = count(array_filter($responses, fn($r) => $r['ok'] ?? false)); $total = count($responses); echo json_encode([ 'ok' => true, 'duration_ms' => round((microtime(true) - $start_total) * 1000), 'summary' => [ 'total' => $total, 'ok' => $ok_count, 'failed' => $total - $ok_count, 'kb_augmented' => !!$wevia_kb_context, 'kb_chars' => $wevia_kb_context ? strlen($wevia_kb_context) : 0 ], 'responses' => $responses ], JSON_PRETTY_PRINT); // ===================================================================== // FUNCTIONS // ===================================================================== function queryWeviaKB($query) { // Step 1: Get embedding via local sovereign embedding endpoint or Cerebras // Simplification: use Qdrant text-search via existing endpoint if exposed // Otherwise use grep-style fallback through wevia knowledge files $context_parts = []; // 1a) Try Qdrant search (vector if embeddings available) $ch = curl_init("http://127.0.0.1:6333/collections/wevia_brain_knowledge/points/scroll"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode(['limit' => 5, 'with_payload' => true]), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_TIMEOUT => 4 ]); $resp = curl_exec($ch); curl_close($ch); $data = @json_decode($resp, true); if (!empty($data['result']['points'])) { foreach ($data['result']['points'] as $pt) { $payload = $pt['payload'] ?? []; $text = $payload['text'] ?? $payload['content'] ?? $payload['summary'] ?? ''; if ($text) $context_parts[] = substr(trim($text), 0, 400); } } // 1b) If KB search returned nothing useful, query other related collections if (empty($context_parts)) { foreach (['kb_consulting_strategy', 'kb_bpmn_patterns', 'wevia_learnings'] as $coll) { $ch = curl_init("http://127.0.0.1:6333/collections/$coll/points/scroll"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode(['limit' => 3, 'with_payload' => true]), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_TIMEOUT => 3 ]); $r = @json_decode(curl_exec($ch), true); curl_close($ch); if (!empty($r['result']['points'])) { foreach ($r['result']['points'] as $pt) { $text = $pt['payload']['text'] ?? $pt['payload']['content'] ?? ''; if ($text) $context_parts[] = "[$coll] " . substr(trim($text), 0, 300); } } } } return empty($context_parts) ? null : implode("\n\n---\n", array_slice($context_parts, 0, 8)); } function callWeviaMaster($message, $kb_context = null) { $system = "Tu es WEVIA Master, l'IA pivot souveraine de WEVAL Consulting. Tu réponds en français professionnel, structuré, concis. Tu maitrises: SAP S/4HANA, Vistex, BPM/BPMN, conseil stratégie, transformation digitale, AI/LLM, cybersécurité, Maghreb business context."; if ($kb_context) { $system .= "\n\n=== Contexte KB WEVIA pertinent ===\n" . $kb_context . "\n=== Fin KB ===\n\nUtilise ce contexte si pertinent dans ta réponse. Cite [KB] si tu en extraies des infos."; } $payload = [ 'model' => 'sovereign', 'messages' => [ ['role' => 'system', 'content' => $system], ['role' => 'user', 'content' => $message] ], 'max_tokens' => 1200, 'temperature' => 0.4 ]; $ch = curl_init('http://127.0.0.1:4000/v1/chat/completions'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_TIMEOUT => 30 ]); $resp = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $data = @json_decode($resp, true); if ($http !== 200 || empty($data['choices'][0]['message']['content'])) { return ['error' => "Sovereign API HTTP $http: " . substr($resp, 0, 200)]; } return [ 'content' => $data['choices'][0]['message']['content'], 'model' => $data['model'] ?? null, 'provider' => $data['provider'] ?? null ]; }