205 lines
7.3 KiB
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
|
|
];
|
|
}
|