114 lines
3.7 KiB
PHP
114 lines
3.7 KiB
PHP
<?php
|
|
/*
|
|
WEVIA MEMORY BRIDGE · Doctrine 141
|
|
Opus · 23avr 21h35
|
|
|
|
Middleware léger pour doter tout chatbot interne de mémoire persistante
|
|
Redis DB 5 + Qdrant long-term, via un appel vers wevia-chat-memory.php
|
|
|
|
Usage:
|
|
require_once __DIR__ . '/wevia-memory-bridge.php';
|
|
$history = wevia_mem_load('claw-code', $user_id); // charge history
|
|
// ... logique métier du chatbot ...
|
|
wevia_mem_save('claw-code', $user_id, $message, $response); // save
|
|
|
|
- Redis DB 5 key format: chatmem:<chat_id>:<user_id>
|
|
- Scope internal = unlimited (no TTL)
|
|
- Scope public = TTL 24h
|
|
- Fail-safe: si Redis down, continue sans mémoire (pas de crash)
|
|
*/
|
|
|
|
if (!function_exists('wevia_mem_load')) {
|
|
|
|
function wevia_mem_user_id(): string {
|
|
// Priorité: JWT cookie > session cookie > IP+UA hash > anon
|
|
$uid = $_COOKIE['weval_chat_session'] ?? '';
|
|
if (!$uid) {
|
|
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
|
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
|
$uid = $ip ? 'anon-' . substr(md5($ip . $ua), 0, 12) : 'anon-default';
|
|
}
|
|
return $uid;
|
|
}
|
|
|
|
function wevia_mem_redis(): ?Redis {
|
|
static $r = null;
|
|
if ($r === null) {
|
|
try {
|
|
$r = new Redis();
|
|
$r->connect('127.0.0.1', 6379, 1.5);
|
|
$r->select(5);
|
|
} catch (Throwable $e) {
|
|
$r = false;
|
|
}
|
|
}
|
|
return $r ?: null;
|
|
}
|
|
|
|
function wevia_mem_key(string $chat_id, string $user_id): string {
|
|
return "chatmem:" . preg_replace('/[^a-z0-9_-]/i', '_', $chat_id) . ":" . $user_id;
|
|
}
|
|
|
|
function wevia_mem_load(string $chat_id, ?string $user_id = null, int $max = 20): array {
|
|
$user_id = $user_id ?? wevia_mem_user_id();
|
|
$r = wevia_mem_redis();
|
|
if (!$r) return [];
|
|
try {
|
|
$raw = $r->get(wevia_mem_key($chat_id, $user_id));
|
|
if (!$raw) return [];
|
|
$arr = json_decode($raw, true) ?: [];
|
|
return array_slice($arr, -$max);
|
|
} catch (Throwable $e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function wevia_mem_save(string $chat_id, ?string $user_id, string $message, string $response, string $scope = 'internal'): bool {
|
|
$user_id = $user_id ?? wevia_mem_user_id();
|
|
$r = wevia_mem_redis();
|
|
if (!$r) return false;
|
|
try {
|
|
$k = wevia_mem_key($chat_id, $user_id);
|
|
$raw = $r->get($k);
|
|
$arr = $raw ? (json_decode($raw, true) ?: []) : [];
|
|
$arr[] = ['role' => 'user', 'content' => substr($message, 0, 4000), 'ts' => date('c')];
|
|
$arr[] = ['role' => 'assistant', 'content' => substr($response, 0, 4000), 'ts' => date('c')];
|
|
// Cap à 500 messages
|
|
$arr = array_slice($arr, -500);
|
|
$r->set($k, json_encode($arr));
|
|
// TTL: internal = none (persistent), public = 24h
|
|
if ($scope === 'public') {
|
|
$r->expire($k, 86400);
|
|
}
|
|
return true;
|
|
} catch (Throwable $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function wevia_mem_context(string $chat_id, ?string $user_id = null, int $max = 10): string {
|
|
// Retourne les N derniers messages comme contexte injecté dans prompt
|
|
$history = wevia_mem_load($chat_id, $user_id, $max);
|
|
if (!$history) return '';
|
|
$out = "=== Conversation history ===\n";
|
|
foreach ($history as $msg) {
|
|
$role = $msg['role'] === 'user' ? 'User' : 'Assistant';
|
|
$content = substr($msg['content'] ?? '', 0, 500);
|
|
$out .= "$role: $content\n";
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
function wevia_mem_stats(string $chat_id, ?string $user_id = null): array {
|
|
$history = wevia_mem_load($chat_id, $user_id, 10000);
|
|
return [
|
|
'messages' => count($history),
|
|
'chat_id' => $chat_id,
|
|
'user_id' => $user_id ?? wevia_mem_user_id(),
|
|
'first_ts' => $history[0]['ts'] ?? null,
|
|
'last_ts' => end($history)['ts'] ?? null
|
|
];
|
|
}
|
|
|
|
} // end if !function_exists
|