Files
html/api/wevia-chat.php
2026-04-17 22:50:02 +02:00

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]);