"message required"]); exit; } if (!$sid) $sid = "anon-" . substr(md5(($_SERVER["REMOTE_ADDR"] ?? "x") . time()), 0, 10); $history = AmbreSessionMemory::context_messages($sid, 12); $turns_before = count($history); $identity = null; $history_full = AmbreSessionMemory::load($sid); foreach ($history_full as $m) { if ($m["role"] === "meta" && strpos($m["content"], "identity:") === 0) { $identity = trim(substr($m["content"], 9)); break; } } $extracted_name = null; $extracted_org = null; if (preg_match('/(?:je\s+m[\'\s]?appelle|mon\s+nom\s+est|je\s+suis|c[\'\s]?est\s+moi|i[\'\s]?m|my\s+name\s+is)\s+([A-ZÀ-Üa-zà-ü][A-ZÀ-Üa-zà-ü\-\s]{1,40}?)(?:\s+(?:de|from|at|chez|pour|travaille|,|\.|!|$))/iu', $msg, $m)) { $extracted_name = trim($m[1]); } if (preg_match('/(?:de|from|chez|at|travaille\s+(?:chez|pour|à))\s+([A-ZÀ-Üa-zà-ü][A-ZÀ-Üa-zà-ü0-9\-\s]{2,40}?)(?:\s*[\.,!]|\s+et|\s*$)/iu', $msg, $m)) { $extracted_org = trim($m[1]); } if (($extracted_name || $extracted_org) && !$identity) { $id_parts = []; if ($extracted_name) $id_parts[] = "nom=$extracted_name"; if ($extracted_org) $id_parts[] = "org=$extracted_org"; $identity = implode(" · ", $id_parts); AmbreSessionMemory::append($sid, "meta", "identity: $identity"); } if ($turns_before === 0 && !$identity) { if ($extracted_name || $extracted_org) { $greeting_sys = "L'utilisateur vient de se présenter. Salue-le chaleureusement en utilisant son nom si connu et son entreprise si connue. Demande-lui comment tu peux l'aider. 2-3 phrases max. Reste en français."; } else { $greeting_sys = "Premier échange. Réponds brièvement à sa question, PUIS demande avec élégance son prénom et son entreprise. Style chaleureux, 3-4 phrases."; } $messages = [["role"=>"system","content"=>"Tu es WEVIA de WEVAL Consulting. $greeting_sys"]]; $messages[] = ["role"=>"user","content"=>$msg]; } else { $sys_parts = [ "Tu es WEVIA, l'IA de WEVAL Consulting.", "Tu mémorises les échanges et t'adaptes au ton et contexte.", ]; if ($identity) { $sys_parts[] = "Identité: $identity. Utilise le nom naturellement."; } $sys_parts[] = "Réponds en français, concis, empathique si émotion détectée."; $sys_parts[] = "Si retour sur sujet antérieur, reconnais. Si changement, adapte-toi."; $messages = [["role"=>"system","content"=>implode(" ", $sys_parts)]]; foreach ($history as $h) { if ($h["role"] !== "meta") $messages[] = $h; } $messages[] = ["role"=>"user","content"=>$msg]; } // === LLM call WITH SEMAPHORE (6σ throttle) === $t0 = microtime(true); $wait_ms_before = 0; $sem_id = AmbreLLMSemaphore::acquire(); $wait_ms_before = round((microtime(true)-$t0)*1000); if (!$sem_id) { // Queue full after 20s wait - fail gracefully echo json_encode([ "response" => "Le service est saturé, réessayez dans quelques secondes.", "provider" => "ambre-session-v3", "intent" => "semaphore_timeout", "session_id" => $sid, "wait_ms" => $wait_ms_before, ], JSON_UNESCAPED_UNICODE); exit; } try { $llm_start = microtime(true); $raw_llm = @file_get_contents("http://127.0.0.1:4000/v1/chat/completions", false, stream_context_create([ "http" => [ "method"=>"POST", "header"=>"Content-Type: application/json\r\n", "content"=>json_encode(["model"=>"fast","messages"=>$messages,"max_tokens"=>1200,"temperature"=>0.5]), "timeout"=>30, ], ])); $llm_elapsed = round((microtime(true)-$llm_start)*1000); } finally { AmbreLLMSemaphore::release($sem_id); } $d = @json_decode($raw_llm, true); $reply = $d["choices"][0]["message"]["content"] ?? ""; if (!$reply) $reply = "Désolé, je n'ai pas pu traiter la demande. Peux-tu reformuler ?"; AmbreSessionMemory::append($sid, "user", $msg); AmbreSessionMemory::append($sid, "assistant", $reply); $summary = AmbreSessionMemory::summary($sid); echo json_encode([ "response" => $reply, "provider" => "ambre-session-v3", "intent" => $turns_before === 0 ? "onboarding" : "contextual_reply", "session_id" => $sid, "turns_in_memory" => $summary["turns"], "history_used" => count($history), "identity" => $identity, "wait_ms" => $wait_ms_before, "llm_ms" => $llm_elapsed ?? 0, "elapsed_ms" => round((microtime(true)-$t0)*1000), ], JSON_UNESCAPED_UNICODE);