Skip to main content
Glama
save.py6.96 kB
"""Save memory tool.""" import logging import time import uuid from typing import TYPE_CHECKING, Any, cast from ..config import get_config from ..context import db, mcp from ..performance import time_operation from ..security.secrets import detect_secrets, format_secret_warning, should_warn_about_secrets from ..security.validators import ( MAX_CONTENT_LENGTH, MAX_ENTITIES_COUNT, MAX_TAGS_COUNT, validate_entity, validate_list_length, validate_string_length, validate_tag, ) from ..storage.models import Memory, MemoryMetadata if TYPE_CHECKING: from sentence_transformers import SentenceTransformer logger = logging.getLogger(__name__) # Optional dependency for embeddings _SentenceTransformer: "type[SentenceTransformer] | None" try: from sentence_transformers import SentenceTransformer _SentenceTransformer = SentenceTransformer SENTENCE_TRANSFORMERS_AVAILABLE = True except ImportError: _SentenceTransformer = None SENTENCE_TRANSFORMERS_AVAILABLE = False # Global model cache to avoid reloading on every request _model_cache: dict[str, Any] = {} def _get_embedding_model(model_name: str) -> "SentenceTransformer | None": """Get cached embedding model or create new one.""" if not SENTENCE_TRANSFORMERS_AVAILABLE or _SentenceTransformer is None: return None if model_name not in _model_cache: try: _model_cache[model_name] = _SentenceTransformer(model_name) except Exception: return None return cast("SentenceTransformer", _model_cache[model_name]) def _generate_embedding(content: str) -> list[float] | None: """Generate embedding for content if embeddings are enabled.""" config = get_config() if not config.enable_embeddings or not SENTENCE_TRANSFORMERS_AVAILABLE: return None model = _get_embedding_model(config.embed_model) if model is None: return None try: embedding = model.encode(content, convert_to_numpy=True) return cast(list[float], embedding.tolist()) except Exception: return None @mcp.tool() @time_operation("save_memory") def save_memory( content: str, tags: list[str] | None = None, entities: list[str] | None = None, source: str | None = None, context: str | None = None, meta: dict[str, Any] | None = None, strength: float | None = None, ) -> dict[str, Any]: """ Save a new memory to short-term storage with automatic preprocessing. The memory will have temporal decay applied and will be forgotten if not used regularly. Frequently accessed memories may be promoted to long-term storage automatically. **Auto-enrichment (v0.6.0)**: If entities or strength are not provided, they will be automatically extracted/calculated from the content using natural language preprocessing. This makes save_memory "just work" for conversational use. Args: content: The content to remember (max 50,000 chars). tags: Tags for categorization (max 50 tags, each max 100 chars). entities: Named entities in this memory (max 100 entities). If None, automatically extracted from content. source: Source of the memory (max 500 chars). context: Context when memory was created (max 1,000 chars). meta: Additional custom metadata. strength: Base strength multiplier (1.0-2.0). If None, automatically calculated based on content importance. Raises: ValueError: If any input fails validation. """ # Input validation content = cast( str, validate_string_length(content, MAX_CONTENT_LENGTH, "content", allow_empty=False) ) if tags is not None: tags = validate_list_length(tags, MAX_TAGS_COUNT, "tags") tags = [validate_tag(tag, f"tags[{i}]") for i, tag in enumerate(tags)] if entities is not None: entities = validate_list_length(entities, MAX_ENTITIES_COUNT, "entities") entities = [validate_entity(entity, f"entities[{i}]") for i, entity in enumerate(entities)] if source is not None: source = cast(str, validate_string_length(source, 500, "source", allow_none=True)) if context is not None: context = cast(str, validate_string_length(context, 1000, "context", allow_none=True)) # Auto-enrichment preprocessing (v0.6.0) config = get_config() enrichment_applied = False if config.enable_preprocessing: from ..preprocessing import EntityExtractor, ImportanceScorer, PhraseDetector # Initialize preprocessing components (cached at module level) phrase_detector = PhraseDetector() entity_extractor = EntityExtractor() importance_scorer = ImportanceScorer() # Detect importance signals phrase_signals = phrase_detector.detect(content) # Auto-extract entities if not provided if entities is None: entities = entity_extractor.extract(content) enrichment_applied = True # Auto-calculate strength if not provided if strength is None: strength = importance_scorer.score( content, entities=entities, importance_marker=phrase_signals["importance_marker"] ) enrichment_applied = True else: # Default strength if preprocessing disabled if strength is None: strength = 1.0 # Validate strength if strength is not None and (strength < 1.0 or strength > 2.0): raise ValueError("strength must be between 1.0 and 2.0") # Secrets detection (if enabled) config = get_config() if config.detect_secrets: matches = detect_secrets(content) if should_warn_about_secrets(matches): warning = format_secret_warning(matches) logger.warning(f"Secrets detected in memory content:\n{warning}") # Note: We still save the memory but warn the user # Create metadata metadata = MemoryMetadata( tags=tags or [], source=source, context=context, extra=meta or {}, ) # Generate ID and embedding memory_id = str(uuid.uuid4()) embed = _generate_embedding(content) # Create memory now = int(time.time()) memory = Memory( id=memory_id, content=content, meta=metadata, created_at=now, last_used=now, use_count=0, embed=embed, entities=entities or [], strength=strength if strength is not None else 1.0, ) # Save to database db.save_memory(memory) return { "success": True, "memory_id": memory_id, "message": f"Memory saved with ID: {memory_id}", "has_embedding": embed is not None, "enrichment_applied": enrichment_applied, "auto_entities": len(entities or []) if enrichment_applied else 0, "calculated_strength": strength, }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/prefrontalsys/mnemex'

If you have feedback or need assistance with the MCP directory API, please join our Discord server