Files
html/api/wevia-memory-api.php
2026-04-16 02:28:32 +02:00

173 lines
6.6 KiB
PHP

<?php
/**
* WEVIA MEMORY API — Persistent cross-session memory via Qdrant
*
* POST ?action=remember {"key":"user_preference","value":"prefers French","context":"chat"}
* GET ?action=recall&q=preference
* GET ?action=list&limit=20
* POST ?action=forget {"key":"old_memory_key"}
* GET ?action=stats
*/
header("Content-Type: application/json; charset=utf-8");
header("Access-Control-Allow-Origin: *");
if ($_SERVER["REQUEST_METHOD"] === "OPTIONS") { http_response_code(200); exit; }
$QDRANT = "http://127.0.0.1:6333";
$COLLECTION = "wevia_memory";
$OLLAMA = "http://127.0.0.1:11434";
$input = json_decode(file_get_contents("php://input"), true) ?: [];
$action = $_GET["action"] ?? $input["action"] ?? "stats";
// Ensure collection exists
function ensureCollection() {
global $QDRANT, $COLLECTION;
$ch = curl_init("$QDRANT/collections/$COLLECTION");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 3]);
$r = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
if ($code !== 200) {
$ch2 = curl_init("$QDRANT/collections/$COLLECTION");
curl_setopt_array($ch2, [
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode(["vectors" => ["size" => 384, "distance" => "Cosine"]]),
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5,
]);
curl_exec($ch2); curl_close($ch2);
}
}
// Embed text via Ollama all-minilm
function embed($text) {
global $OLLAMA;
$ch = curl_init("$OLLAMA/api/embeddings");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(["model" => "all-minilm", "prompt" => $text]),
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10,
]);
$r = curl_exec($ch); curl_close($ch);
$d = json_decode($r, true);
return $d["embedding"] ?? null;
}
ensureCollection();
switch ($action) {
case "remember":
$key = $input["key"] ?? "mem_" . time();
$value = $input["value"] ?? "";
$context = $input["context"] ?? "general";
$timestamp = date("c");
if (empty($value)) { echo json_encode(["error" => "value required"]); exit; }
$vector = embed($value);
if (!$vector) { echo json_encode(["error" => "embedding failed"]); exit; }
$point = [
"id" => abs(crc32($key)),
"vector" => $vector,
"payload" => [
"key" => $key,
"value" => $value,
"context" => $context,
"timestamp" => $timestamp,
"type" => "memory",
],
];
$ch = curl_init("$QDRANT/collections/wevia_memory/points");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode(["points" => [$point]]),
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5,
]);
$r = curl_exec($ch); curl_close($ch);
echo json_encode(["status" => "remembered", "key" => $key, "id" => abs(crc32($key))]);
break;
case "recall":
$q = $_GET["q"] ?? $input["query"] ?? "";
$limit = (int)($_GET["limit"] ?? 5);
if (empty($q)) { echo json_encode(["error" => "query required"]); exit; }
$vector = embed($q);
if (!$vector) { echo json_encode(["error" => "embedding failed"]); exit; }
$ch = curl_init("$QDRANT/collections/wevia_memory/points/search");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(["vector" => $vector, "limit" => $limit, "with_payload" => true]),
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5,
]);
$r = curl_exec($ch); curl_close($ch);
$d = json_decode($r, true);
$memories = [];
foreach ($d["result"] ?? [] as $pt) {
$memories[] = [
"key" => $pt["payload"]["key"] ?? "",
"value" => $pt["payload"]["value"] ?? "",
"context" => $pt["payload"]["context"] ?? "",
"score" => round($pt["score"], 3),
"timestamp" => $pt["payload"]["timestamp"] ?? "",
];
}
echo json_encode(["query" => $q, "memories" => $memories, "count" => count($memories)]);
break;
case "list":
$limit = (int)($_GET["limit"] ?? 20);
$ch = curl_init("$QDRANT/collections/wevia_memory/points/scroll");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(["limit" => $limit, "with_payload" => true]),
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5,
]);
$r = curl_exec($ch); curl_close($ch);
$d = json_decode($r, true);
$memories = [];
foreach ($d["result"]["points"] ?? [] as $pt) {
$memories[] = $pt["payload"];
}
echo json_encode(["memories" => $memories, "count" => count($memories)]);
break;
case "forget":
$key = $input["key"] ?? "";
if (empty($key)) { echo json_encode(["error" => "key required"]); exit; }
$id = abs(crc32($key));
$ch = curl_init("$QDRANT/collections/wevia_memory/points/delete");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(["points" => [$id]]),
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5,
]);
curl_exec($ch); curl_close($ch);
echo json_encode(["status" => "forgotten", "key" => $key]);
break;
case "stats":
$ch = curl_init("$QDRANT/collections/wevia_memory");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 3]);
$r = curl_exec($ch); curl_close($ch);
$d = json_decode($r, true);
echo json_encode([
"collection" => "wevia_memory",
"points" => $d["result"]["points_count"] ?? 0,
"vectors" => $d["result"]["vectors_count"] ?? 0,
"status" => $d["result"]["status"] ?? "unknown",
]);
break;
}