Files
html/api/ambre-session-chat.php
opus 9c69db151f
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
auto-sync-0105
2026-04-22 01:05:02 +02:00

131 lines
5.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);