space / src /xblago /core /memory.py
RomanMaloev's picture
Deploy from GitHub Actions
9440b2c verified
Raw History Blame Contribute Delete
3.78 kB
from __future__ import annotations
import json
import os
import tempfile
import re
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any
SENSITIVE_MEMORY_TERMS = (
"password", "пароль", "passcode", "token", "токен", "secret", "секрет",
"cvv", "cvc", "pin", "пин", "card number", "номер карты", "bank login",
"логин банка", "api key", "ключ api", "private key", "приватный ключ",
)
def is_sensitive_memory(key: str, value: Any) -> bool:
text = f"{key} {value}".lower()
return any(term in text for term in SENSITIVE_MEMORY_TERMS)
def relevant_memory(memory: dict[str, Any], text: str, limit: int = 12) -> dict[str, Any]:
"""Return facts useful for this turn, while keeping the prompt small."""
if not memory:
return {}
words = set(re.findall(r"[\w-]{3,}", text.lower()))
aliases = {
"name": {"имя", "зовут", "name"},
"city": {"город", "живу", "city", "live"},
"timezone": {"часовой", "пояс", "timezone"},
"work": {"работа", "работаю", "work", "job"},
"likes": {"люблю", "нравится", "любимое", "like", "love"},
"preferences": {"предпочитаю", "удобнее", "prefer"},
}
ranked = []
for key, value in memory.items():
haystack = f"{key} {value}".lower()
score = sum(1 for word in words if word in haystack)
score += sum(2 for word in aliases.get(str(key), set()) if word in words)
ranked.append((score, str(key), value))
ranked.sort(key=lambda item: (-item[0], item[1]))
return {key: value for score, key, value in ranked[:limit] if score > 0}
class MemoryStore(ABC):
@abstractmethod
def all(self) -> dict[str, Any]:
raise NotImplementedError
@abstractmethod
def set(self, key: str, value: Any) -> None:
raise NotImplementedError
@abstractmethod
def delete(self, key: str) -> bool:
raise NotImplementedError
@abstractmethod
def clear(self) -> None:
raise NotImplementedError
class JsonMemoryStore(MemoryStore):
def __init__(self, path: Path) -> None:
self.path = path
def all(self) -> dict[str, Any]:
if not self.path.exists():
return {}
with self.path.open("r", encoding="utf-8") as file:
data = json.load(file)
if not isinstance(data, dict):
return {}
return data
def set(self, key: str, value: Any) -> None:
data = self.all()
data[key] = value
self._write(data)
def delete(self, key: str) -> bool:
data = self.all()
if key not in data:
return False
del data[key]
self._write(data)
return True
def clear(self) -> None:
self._write({})
def _write(self, data: dict[str, Any]) -> None:
self.path.parent.mkdir(parents=True, exist_ok=True)
fd, temporary_path = tempfile.mkstemp(
prefix=f".{self.path.name}.",
suffix=".tmp",
dir=self.path.parent,
text=True,
)
try:
with os.fdopen(fd, "w", encoding="utf-8") as file:
json.dump(data, file, ensure_ascii=False, indent=2, sort_keys=True)
file.write("\n")
file.flush()
os.fsync(file.fileno())
os.replace(temporary_path, self.path)
except Exception:
try:
os.unlink(temporary_path)
except FileNotFoundError:
pass
raise
def default_memory_path() -> Path:
return Path.home() / ".xblago" / "personal_assistant_memory.json"