Files
html/api/cdp-broadcast.php

205 lines
7.3 KiB
PHP

<?php
// API: /api/cdp-broadcast.php v2
// Wave 311 - WEVIA Master pivot + KB-augmented + CDP optional fallback
// Doctrine: WEVIA Master = patron, Chrome CDP = ground-truth scraping optionnel
header('Content-Type: application/json');
header('Cache-Control: no-store');
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || empty($input['message']) || empty($input['providers'])) {
echo json_encode(['ok' => 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
];
}