rtm-class-ai / src /agent /infra /memory_store.py
nbintang
initialize projects
62516b8
Raw
History Blame Contribute Delete
3.5 kB
from __future__ import annotations
from collections import defaultdict
from datetime import UTC, datetime
from typing import Any
from uuid import uuid4
from langchain_core.documents import Document
from src.config import settings
class LongTermMemoryStore:
def __init__(self) -> None:
self._fallback: dict[str, list[Document]] = defaultdict(list)
self._init_warning: str | None = None
self._vectorstore = None
try:
from langchain_chroma import Chroma
self._vectorstore = Chroma(
collection_name=settings.agent_memory_collection,
persist_directory=settings.chroma_persist_dir,
)
except Exception as exc:
self._init_warning = (
f"Long-term vector memory fallback mode: {exc}"
)
@property
def init_warning(self) -> str | None:
return self._init_warning
def remember_fact(
self,
*,
user_id: str,
fact: str,
memory_type: str = "general",
source: str = "agent_memory",
extra_metadata: dict[str, Any] | None = None,
) -> str:
cleaned = fact.strip()
if not cleaned:
return ""
memory_id = f"mem-{uuid4().hex}"
metadata: dict[str, Any] = {
"memory_id": memory_id,
"user_id": user_id,
"memory_type": memory_type,
"created_at": datetime.now(UTC).isoformat(),
"source": source,
}
if extra_metadata:
metadata.update(extra_metadata)
doc = Document(page_content=cleaned, metadata=metadata)
self._fallback[user_id].append(doc)
if self._vectorstore is not None:
try:
self._vectorstore.add_documents([doc], ids=[memory_id])
except Exception:
# Keep service available even if vector persistence fails.
pass
return memory_id
def recall_user_facts(
self,
*,
user_id: str,
query: str,
limit: int = 5,
) -> list[Document]:
safe_limit = max(1, min(limit, 20))
where = {"user_id": user_id}
if self._vectorstore is not None and query.strip():
try:
docs = self._vectorstore.similarity_search(
query.strip(),
k=safe_limit,
filter=where,
)
if docs:
return docs
except Exception:
pass
if self._vectorstore is not None:
try:
payload = self._vectorstore.get(
where=where,
limit=safe_limit,
include=["documents", "metadatas"],
)
documents = payload.get("documents", [])
metadatas = payload.get("metadatas", [])
docs: list[Document] = []
for text, metadata in zip(documents, metadatas):
if text:
docs.append(
Document(
page_content=text,
metadata=dict(metadata or {}),
)
)
if docs:
return docs
except Exception:
pass
return self._fallback.get(user_id, [])[-safe_limit:]