Skip to main content
Glama
analyze_message.py9.21 kB
"""Analyze message tool for conversational memory activation. Helper tool that analyzes user messages to determine if they contain memory-worthy content and provides suggested parameters for save_memory. This is Track 2 of the two-track MCP approach for conversational activation. Updated in v0.7.5 to use the new activation module with configurable patterns and weighted sigmoid confidence calculation. """ from __future__ import annotations import logging from typing import TYPE_CHECKING, Any from ..config import get_config from ..context import mcp from ..performance import time_operation if TYPE_CHECKING: from ..activation.config import ActivationConfig from ..activation.patterns import PatternMatcher logger = logging.getLogger(__name__) # Module-level cache for config and matcher (lazy initialization) _activation_config: ActivationConfig | None = None _pattern_matcher: PatternMatcher | None = None def _get_default_activation_config() -> ActivationConfig: """Create default activation config with sensible defaults. Used when YAML config file is not available. These defaults match the activation.yaml.example file for consistency. Returns: ActivationConfig with production-ready defaults """ from ..activation.config import ( ActivationConfig, ConfidenceThreshold, PatternLibrary, ) patterns = PatternLibrary( explicit_save_triggers=[ "remember this", "remember that", "don't forget", "do not forget", "save this", "keep in mind", "make a note", "note that", # User preferences (key patterns for "I prefer" support) "my preference is", "i prefer", "i always", "i never", "i like to", "i tend to", # Decisions "i've decided", "i decided to", "my decision is", "my choice is", ], explicit_recall_triggers=[ "what did i say", "what did we discuss", "do you remember", "remind me", "recall", "my preference", "my choice", "what's my", "what is my", ], importance_markers=[ "critical", "crucial", "essential", "must", "important", "vital", "urgent", "key", "always", "never", ], exclusion_patterns=[ "what is", "who is", "how do", "how to", "tell me about", "explain", "can you explain", ], uncertainty_markers=[ "maybe", "might", "not sure", "perhaps", "possibly", "probably", "i think", ], case_sensitive=False, partial_match=True, ) thresholds = ConfidenceThreshold( auto_save_min=0.7, auto_recall_min=0.7, clarification_min=0.4, clarification_max=0.7, ) weights = { "explicit_save_request": 5.0, "explicit_recall_request": 4.0, "preference_statement": 4.0, "critical_marker": 3.0, "important_marker": 2.0, "uncertainty_marker": -3.0, "entity_count": 0.8, } return ActivationConfig( patterns=patterns, thresholds=thresholds, weights=weights, bias=-2.0, ) def _get_activation_config() -> ActivationConfig: """Get or create activation config (cached). Tries to load from YAML config file first, falls back to defaults. Config is cached at module level for performance. Returns: ActivationConfig instance """ global _activation_config if _activation_config is not None: return _activation_config from ..activation.config import load_activation_config try: _activation_config = load_activation_config() logger.info("Loaded activation config from YAML file") except FileNotFoundError: logger.info("No activation.yaml found, using default config") _activation_config = _get_default_activation_config() return _activation_config def _get_pattern_matcher() -> PatternMatcher: """Get or create pattern matcher (cached). Creates PatternMatcher from current activation config. Matcher is cached at module level for performance. Returns: PatternMatcher instance """ global _pattern_matcher if _pattern_matcher is not None: return _pattern_matcher from ..activation.patterns import PatternMatcher config = _get_activation_config() _pattern_matcher = PatternMatcher(config.patterns) return _pattern_matcher def _make_error_response(reason: str) -> dict[str, Any]: """Create a minimal error response with consistent structure. Args: reason: Human-readable explanation of why analysis failed/skipped Returns: Dictionary with all expected fields set to safe defaults """ return { "should_save": False, "confidence": 0.0, "suggested_entities": [], "suggested_tags": [], "suggested_strength": 1.0, "reasoning": reason, "phrase_signals": {}, } @mcp.tool() @time_operation("analyze_message") def analyze_message(message: str) -> dict[str, Any]: """ Analyze a message to determine if it contains memory-worthy content. Returns activation signals and suggested parameters for save_memory. This tool helps the LLM decide whether to save information without explicit "remember this" commands. **Decision Support (v0.6.0)**: Provides confidence scores and reasoning to help Claude determine if save_memory should be called. High confidence (>0.7) suggests automatic save; medium confidence (0.4-0.7) suggests asking user first. **Activation Module (v0.7.5)**: Now uses the configurable activation module with weighted sigmoid confidence calculation. Supports "I prefer" and other preference patterns from activation.yaml configuration. Args: message: User message to analyze Returns: Dictionary containing: - should_save (bool): Recommendation to save - confidence (float): 0.0-1.0 confidence in recommendation - suggested_entities (list[str]): Detected entities - suggested_tags (list[str]): Suggested tags - suggested_strength (float): Calculated importance (1.0-2.0) - reasoning (str): Explanation of decision - phrase_signals (dict): Detected signals for transparency Example: >>> result = analyze_message("I prefer PostgreSQL for databases") >>> result["should_save"] True >>> result["confidence"] 0.73 >>> "preference" in result["reasoning"].lower() True """ # Input validation if message is None: logger.warning("analyze_message called with None message") return _make_error_response("Invalid input: message is None") if not isinstance(message, str): logger.warning(f"analyze_message called with non-string: {type(message)}") return _make_error_response(f"Invalid input: expected string, got {type(message).__name__}") # Log analysis request (truncate long messages) msg_preview = message[:100] + "..." if len(message) > 100 else message logger.debug(f"Analyzing message: {msg_preview!r}") config = get_config() if not config.enable_preprocessing: logger.debug("Preprocessing disabled, returning minimal response") return _make_error_response("Preprocessing disabled in configuration") try: # Use new activation module from ..activation.detectors import detect_save_intent activation_config = _get_activation_config() matcher = _get_pattern_matcher() # Run detection analysis = detect_save_intent(message, activation_config, matcher) # Log decision for debugging if analysis.should_save: logger.debug( f"Save recommended: confidence={analysis.confidence:.3f}, " f"entities={analysis.suggested_entities}" ) else: logger.debug(f"No save: confidence={analysis.confidence:.3f}") # Convert MessageAnalysis to dict response return { "should_save": analysis.should_save, "confidence": analysis.confidence, "suggested_entities": analysis.suggested_entities, "suggested_tags": analysis.suggested_tags, "suggested_strength": analysis.suggested_strength, "reasoning": analysis.reasoning, "phrase_signals": analysis.phrase_signals, } except Exception as e: # Log unexpected errors but don't crash logger.error(f"Error in analyze_message: {e}", exc_info=True) return _make_error_response(f"Analysis error: {type(e).__name__}")

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