131 lines
5.0 KiB
PHP
131 lines
5.0 KiB
PHP
<?php
|
||
/**
|
||
* ambre-session-chat.php v3 · onboarding + empathy + identity + LLM semaphore (6σ)
|
||
* POST {message, session_id}
|
||
* - Semaphore-protected LLM call (max 5 concurrent)
|
||
* - Automatic identity extraction
|
||
* - Memory persistence
|
||
*/
|
||
require_once __DIR__ . "/ambre-session-memory.php";
|
||
require_once __DIR__ . "/ambre-llm-semaphore.php";
|
||
|
||
header("Content-Type: application/json; charset=utf-8");
|
||
|
||
$raw = file_get_contents("php://input");
|
||
$in = json_decode($raw, true) ?: $_POST;
|
||
$msg = trim($in["message"] ?? "");
|
||
$sid = trim($in["session_id"] ?? "");
|
||
|
||
if (!$msg) { echo json_encode(["error"=>"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);
|