185 lines
8.1 KiB
PHP
185 lines
8.1 KiB
PHP
<?php
|
|
// WEVIA Chat V52 - Orchestrator-first + capability detection
|
|
// V52 17avr: detect CAPABILITY keywords (code/schema/pdf/react/svg/mermaid)
|
|
// → if capability detected = skip orchestrator, go direct to LLM with capable prompt
|
|
// → else if business intent match = orchestrator data reelle
|
|
// → else LLM fallback (v3)
|
|
header('Content-Type: application/json');
|
|
header('Access-Control-Allow-Origin: *');
|
|
header('Access-Control-Allow-Headers: Content-Type');
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$msg = trim($input['message'] ?? $input['msg'] ?? $input['text'] ?? '');
|
|
if (!$msg) { echo json_encode(['response' => 'Message vide', 'provider' => 'error']); exit; }
|
|
|
|
$msg_lower = mb_strtolower($msg);
|
|
|
|
// === V52 CAPABILITY DETECTION ===
|
|
// If query asks for code/schema/document GENERATION, skip orchestrator
|
|
$capability_keywords = [
|
|
'react', 'composant', 'component', 'typescript', 'jsx', 'tsx',
|
|
'mermaid', 'svg', 'schema', 'schéma', 'diagramme', 'architecture',
|
|
'pdf', 'document', 'cahier.*charges', 'specs?\b', 'spécifications?',
|
|
'dessine', 'draw', 'genere.*pdf', 'genere.*schema', 'genere.*code',
|
|
'redige', 'rédige', 'ecris.*code', 'écris.*code', 'write.*code',
|
|
'python|javascript|php|sql.*query', 'fonction|function',
|
|
'html|css|tailwind|shadcn'
|
|
];
|
|
$is_capability = false;
|
|
foreach ($capability_keywords as $kw) {
|
|
if (preg_match('/\b' . $kw . '\b/iu', $msg_lower)) {
|
|
$is_capability = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$t0 = microtime(true);
|
|
|
|
// === Route 1: CAPABILITY mode → direct LLM with enhanced prompt ===
|
|
if ($is_capability) {
|
|
$ch = curl_init('http://127.0.0.1:4000/v1/chat/completions');
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_POST => true,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 30,
|
|
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
|
CURLOPT_POSTFIELDS => json_encode([
|
|
'model' => 'deep',
|
|
'messages' => [
|
|
['role' => 'system', 'content' => "Tu es WEVIA, IA souveraine de WEVAL Consulting Casablanca. Tu DOIS générer le contenu demandé de qualité professionnelle : code (React/TS/PHP/Python/SQL avec blocs markdown ```lang), schémas (Mermaid ```mermaid ou SVG ```svg inline), documents (cahier des charges structuré avec sections ##), spécifications techniques détaillées. NE REFUSE JAMAIS de générer. Utilise contexte WEVAL : PostgreSQL Ethica 146694 HCPs, Authentik SSO, WEVADS email marketing, Twenty CRM, n8n workflows. Format markdown clair, professionnel, complet."],
|
|
['role' => 'user', 'content' => $msg]
|
|
],
|
|
'max_tokens' => 3000,
|
|
'temperature' => 0.4,
|
|
])
|
|
]);
|
|
$resp = curl_exec($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$time_ms = round(curl_getinfo($ch, CURLINFO_TOTAL_TIME) * 1000);
|
|
curl_close($ch);
|
|
|
|
if ($code === 200) {
|
|
$d = json_decode($resp, true);
|
|
$text = $d['choices'][0]['message']['content'] ?? '';
|
|
$prov = $d['provider'] ?? 'sovereign';
|
|
if ($text) {
|
|
echo json_encode([
|
|
'response' => $text,
|
|
'provider' => $prov . ' (capability)',
|
|
'thinking' => ['Capability mode: code/schema/document generation'],
|
|
'duration_ms' => $time_ms
|
|
]);
|
|
exit;
|
|
}
|
|
}
|
|
// If capability LLM fails, fall through to orchestrator as last resort
|
|
}
|
|
|
|
// === Route 2: Orchestrator intent detection (business data) ===
|
|
// V51 PUBLIC SCOPE: route towards bridged public orchestrator (whitelist intents only)
|
|
$orch_url = 'http://127.0.0.1/api/wevia-sse-orchestrator-public.php?msg=' . urlencode($msg);
|
|
$ch = curl_init($orch_url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 12,
|
|
CURLOPT_CONNECTTIMEOUT => 3,
|
|
CURLOPT_HTTPHEADER => ['Host: weval-consulting.com'],
|
|
]);
|
|
$sse_body = curl_exec($ch);
|
|
$orch_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
$exec_results = [];
|
|
$llm_synthesis = null;
|
|
|
|
if ($orch_code === 200 && $sse_body) {
|
|
foreach (explode("\n", $sse_body) as $line) {
|
|
if (strpos($line, 'data: ') !== 0) continue;
|
|
$evt = @json_decode(substr($line, 6), true);
|
|
if (!$evt || !isset($evt['type'])) continue;
|
|
if ($evt['type'] === 'exec_result' && !empty($evt['id']) && !empty($evt['result'])) {
|
|
$exec_results[$evt['id']] = substr($evt['result'], 0, 1500);
|
|
} elseif ($evt['type'] === 'llm_synthesis' && !empty($evt['text'])) {
|
|
$llm_synthesis = $evt['text'];
|
|
}
|
|
}
|
|
}
|
|
|
|
$orch_ms = round((microtime(true) - $t0) * 1000);
|
|
|
|
// Require at least 1 business intent AND query has business keyword
|
|
$has_business_intent = !empty($exec_results);
|
|
$has_business_keyword = preg_match('/\b(combien|status|etat|nombre|liste|show|afficher|how\s+many|count)\b/iu', $msg_lower);
|
|
|
|
if ($has_business_intent && $has_business_keyword) {
|
|
$combined = "Data WEVIA (intents executes: " . implode(', ', array_keys($exec_results)) . "):\n\n";
|
|
foreach ($exec_results as $id => $out) {
|
|
$combined .= "**{$id}:**\n" . trim($out) . "\n\n";
|
|
}
|
|
if ($llm_synthesis) {
|
|
$combined .= "---\n" . $llm_synthesis;
|
|
}
|
|
echo json_encode([
|
|
'response' => $combined,
|
|
'provider' => 'orchestrator',
|
|
'intents_fired' => array_keys($exec_results),
|
|
'thinking' => ['Orchestrator intents detected + business keyword'],
|
|
'duration_ms' => $orch_ms
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
// === V53 FIX: Honor public orchestrator REFUSAL message ===
|
|
// If orchestrator returned llm_synthesis containing a refusal (blocked topic) → return it
|
|
// DO NOT fall back to LLM (which would hallucinate the blocked topic)
|
|
if ($llm_synthesis && preg_match('/(pas\s+disponible\s+publiquement|information\s+(reservee|confidentielle)|contactez\s+sales@weval|access_restricted|non\s+autoris)/iu', $llm_synthesis)) {
|
|
echo json_encode([
|
|
'response' => $llm_synthesis,
|
|
'provider' => 'orchestrator-public-guard',
|
|
'thinking' => ['Public orchestrator blocked topic — refusal honored (V53)'],
|
|
'duration_ms' => $orch_ms
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
// === Route 3: Fallback sovereign LLM direct ===
|
|
$ch = curl_init('http://127.0.0.1:4000/v1/chat/completions');
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_POST => true,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 15,
|
|
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
|
CURLOPT_POSTFIELDS => json_encode([
|
|
'model' => 'auto',
|
|
'messages' => [
|
|
['role' => 'system', 'content' => 'Tu es WEVIA, IA souveraine de WEVAL Consulting Casablanca, cabinet transformation digitale. Reponds en francais, professionnel, concis. CONTEXTE WEVAL (utilise UNIQUEMENT ces faits, NE JAMAIS INVENTER) : (1) Ethica = base B2B pharma de 146694 medecins/HCPs au Maghreb (Algerie, Maroc, Tunisie) utilisee pour campagnes email marketing sante ; (2) WEVADS = plateforme email marketing propriataire ; (3) SAP Ecosystem Partner. Si la question concerne des DONNEES INTERNES precises (HCPs par pays, credentials, warmup accounts, pmta, ethica live details), reponds : \"Cette information est confidentielle. Contactez sales@weval-consulting.com.\" NE JAMAIS inventer de chiffres ou decrire Ethica comme autre chose que la base HCP B2B.'],
|
|
['role' => 'user', 'content' => $msg]
|
|
],
|
|
'max_tokens' => 1500,
|
|
'temperature' => 0.3,
|
|
])
|
|
]);
|
|
$resp = curl_exec($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$time_ms = round(curl_getinfo($ch, CURLINFO_TOTAL_TIME) * 1000);
|
|
curl_close($ch);
|
|
|
|
if ($code === 200) {
|
|
$d = json_decode($resp, true);
|
|
$text = $d['choices'][0]['message']['content'] ?? '';
|
|
$prov = $d['provider'] ?? 'sovereign';
|
|
if ($text) {
|
|
echo json_encode([
|
|
'response' => $text,
|
|
'provider' => $prov . ' (fallback)',
|
|
'thinking' => ['No intent match, LLM fallback'],
|
|
'duration_ms' => $time_ms,
|
|
'orch_ms' => $orch_ms
|
|
]);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
echo json_encode(['response' => 'Tous les providers sont occupes. Reessayez.', 'provider' => 'busy', 'duration_ms' => $time_ms]);
|