173 lines
6.6 KiB
PHP
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;
|
|
}
|