Maxun / src /magentic_ui /learning /memory_provider.py
AUXteam's picture
Upload folder using huggingface_hub
6e38ce1 verified
Raw
History Blame Contribute Delete
7.26 kB
import os
import hashlib
import base64
from typing import Optional, Dict, ClassVar
from loguru import logger
from pathlib import Path
from autogen_ext.experimental.task_centric_memory import (
MemoryController,
MemoryControllerConfig,
MemoryBankConfig,
)
from autogen_ext.experimental.task_centric_memory.utils import PageLogger
from autogen_core.models import ChatCompletionClient
MEMORY_SUBDIR = "memory_bank"
LOG_SUBDIR = "pagelogs"
class MemoryControllerProvider:
"""Singleton provider for memory controller instances"""
_instance: ClassVar[Optional["MemoryControllerProvider"]] = None
_memory_controllers: Dict[str, MemoryController] = {}
_internal_workspace_root: Optional[Path] = None
_external_workspace_root: Optional[Path] = None
_inside_docker: bool = False
def __new__(
cls,
internal_workspace_root: Optional[Path] = None,
external_workspace_root: Optional[Path] = None,
inside_docker: bool = False,
):
if cls._instance is None:
cls._instance = super(MemoryControllerProvider, cls).__new__(cls)
cls._instance._memory_controllers = {}
cls._instance._internal_workspace_root = internal_workspace_root
cls._instance._external_workspace_root = external_workspace_root
cls._instance._inside_docker = inside_docker
return cls._instance
def __init__(
self,
internal_workspace_root: Optional[Path] = None,
external_workspace_root: Optional[Path] = None,
inside_docker: bool = False,
):
"""
Initialize the memory controller provider with paths
Args:
internal_workspace_root (Path, optional): Path to workspace root inside docker
external_workspace_root (Path, optional): Path to workspace root on host
inside_docker (bool, optional): Whether code is running inside Docker. Default: False
"""
if internal_workspace_root is not None:
self._internal_workspace_root = internal_workspace_root
if external_workspace_root is not None:
self._external_workspace_root = external_workspace_root
self._inside_docker = inside_docker
root = (
self._internal_workspace_root
if self._inside_docker
else self._external_workspace_root
)
if isinstance(root, Path):
memory_dir = root / MEMORY_SUBDIR
log_dir = root / LOG_SUBDIR
memory_dir.mkdir(exist_ok=True, parents=True)
log_dir.mkdir(exist_ok=True, parents=True)
try:
self.validate_path_safety(memory_dir, root)
self.validate_path_safety(log_dir, root)
except ValueError as e:
logger.error(
f"Path safety validation failed during initialization: {e}"
)
raise ValueError(f"Security violation: {e}")
else:
logger.warning("Invalid root path: root must be a Path object")
@staticmethod
def get_safe_key(memory_controller_key: str) -> str:
"""Convert a user ID into a filesystem-safe key"""
hash_obj = hashlib.sha256(memory_controller_key.encode("utf-8"))
safe_id = (
base64.urlsafe_b64encode(hash_obj.digest()[:16]).decode("utf-8").rstrip("=")
)
return safe_id
@staticmethod
def validate_path_safety(path: Path, base_dir: Path) -> bool:
"""
Validate that a path is within the expected base directory.
Helps prevent directory traversal attacks.
Args:
path (Path): The path to validate
base_dir (Path): The base directory that should contain the path
Returns:
bool: True if path is safe
Raises:
ValueError: If the path is outside the base directory
"""
resolved_path = os.path.realpath(path)
resolved_base = os.path.realpath(base_dir)
if not resolved_path.startswith(resolved_base):
raise ValueError(f"Path validation failed: {path} is outside of {base_dir}")
return True
def get_path(self, subdir: str, safe_key: str) -> Optional[Path]:
"""Get path based on current context (Docker or host)"""
root = (
self._internal_workspace_root
if self._inside_docker
else self._external_workspace_root
)
if isinstance(root, Path):
full_path = root / subdir / safe_key
full_path.mkdir(exist_ok=True, parents=True)
try:
self.validate_path_safety(full_path, root / subdir)
return full_path
except ValueError:
raise ValueError("Invalid memory controller key")
logger.warning(
"Memory controller provider root path is not a valid Path object"
)
return None
def get_memory_controller(
self,
memory_controller_key: str,
client: ChatCompletionClient,
reset: bool = False,
) -> MemoryController:
"""Get or create a memory controller for the specified user"""
safe_key = self.get_safe_key(memory_controller_key)
if safe_key in self._memory_controllers and not reset:
return self._memory_controllers[safe_key]
memory_path = self.get_path(MEMORY_SUBDIR, safe_key)
log_path = self.get_path(LOG_SUBDIR, safe_key)
try:
page_logger = PageLogger(config={"level": "INFO", "path": str(log_path)})
memory_bank_config = MemoryBankConfig(
path=str(memory_path),
relevance_conversion_threshold=float(
os.environ.get("MEMORY_RELEVANCE_THRESHOLD", "1.7")
),
)
memory_controller_config = MemoryControllerConfig(
generalize_task=False,
revise_generalized_task=False,
generate_topics=False,
validate_memos=True,
max_memos_to_retrieve=1,
MemoryBank=memory_bank_config,
)
memory_controller = MemoryController(
reset=reset,
client=client,
logger=page_logger,
config=memory_controller_config,
)
self._memory_controllers[safe_key] = memory_controller
return memory_controller
except Exception as e:
logger.error(f"Error creating memory controller: {e}")
raise
def close_memory_controller(self, memory_controller_key: str) -> None:
"""Close a memory controller and clean up resources"""
safe_key = self.get_safe_key(memory_controller_key)
if safe_key in self._memory_controllers:
logger.info(f"Closing memory controller (safe key: {safe_key})")
del self._memory_controllers[safe_key]
def close_all_memory_controllers(self) -> None:
"""Close all memory controllers"""
logger.info(
f"Closing all memory controllers ({len(self._memory_controllers)} total)"
)
self._memory_controllers.clear()