Files
html/api/wevia-chat.php
2026-04-18 18:00:03 +02:00

185 lines
8.5 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 WEVAL au Maghreb (Algerie, Maroc, Tunisie) pour campagnes email marketing sante ; (2) WEVADS = plateforme email marketing proprietaire WEVAL ; (3) SAP Ecosystem Partner ; (4) DeerFlow = outil research agent open-source multi-agents WEVAL (PAS marketing automation) ; (5) Paperclip = plateforme WEVAL pour agents IA ; (6) WEVIA = IA souveraine WEVAL ; (7) WEVCODE = assistant code souverain WEVAL ; (8) WEDROID = diagnostic backend WEVAL ; (9) DSH-PREDICT = predictive analytics WEVAL ; (10) WePredict = intelligence competitive WEVAL. Si un terme evoque un produit WEVAL inconnu, refuse poliment plutot que dinventer. 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]);