feat(memory_system): add configurable status message verbosity levels

Introduce status_emit_level to control status message verbosity, allowing
users to select Basic, Intermediate, or Detailed output; refactor all
status emission calls to respect this setting, improving clarity and
customizability of system feedback for different user needs.
This commit is contained in:
mtayfur
2025-11-07 00:19:50 +03:00
parent eca65fef43
commit ff6052fd41
2 changed files with 64 additions and 16 deletions

View File

@@ -15,7 +15,7 @@ import time
from collections import OrderedDict
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union
import numpy as np
from fastapi import Request
@@ -64,6 +64,18 @@ class Constants:
# Content Display
CONTENT_PREVIEW_LENGTH = 100 # Maximum length for content preview display
# Status Emit Levels
STATUS_LEVEL_BASIC = 0 # Maps to "Basic" - Show only summary counts
STATUS_LEVEL_INTERMEDIATE = 1 # Maps to "Intermediate" - Show summaries and key details
STATUS_LEVEL_DETAILED = 2 # Maps to "Detailed" - Show everything including full diagnostics
# Mapping from enum string values to numeric levels for comparison
STATUS_LEVEL_MAP = {
"Basic": 0,
"Intermediate": 1,
"Detailed": 2,
}
class Prompts:
"""Container for all LLM prompts used in the memory system."""
@@ -191,6 +203,13 @@ Explanation: Query seeks general technical explanation without personal context.
class Models:
"""Container for all Pydantic models used in the memory system."""
class StatusEmitLevel(str, Enum):
"""Verbosity levels for status message emission - selectable as dropdown with title case strings."""
BASIC = "Basic"
INTERMEDIATE = "Intermediate"
DETAILED = "Detailed"
class MemoryOperationType(Enum):
CREATE = "CREATE"
UPDATE = "UPDATE"
@@ -710,6 +729,7 @@ CANDIDATE MEMORIES:
emitter,
f"🤖 LLM Analyzing {len(llm_candidates)} Memories for Relevance",
done=False,
level=Constants.STATUS_LEVEL_INTERMEDIATE,
)
logger.info(f"Using LLM reranking: {decision_reason}")
@@ -717,7 +737,9 @@ CANDIDATE MEMORIES:
if not selected_memories:
logger.info("📭 No relevant memories after LLM analysis")
await self.memory_system._emit_status(emitter, f"📭 No Relevant Memories After LLM Analysis", done=True)
await self.memory_system._emit_status(
emitter, f"📭 No Relevant Memories After LLM Analysis", done=True, level=Constants.STATUS_LEVEL_INTERMEDIATE
)
return selected_memories, analysis_info
else:
logger.info(f"Skipping LLM reranking: {decision_reason}")
@@ -730,6 +752,7 @@ CANDIDATE MEMORIES:
emitter,
f"🎯 {retrieval_method} Memory Retrieval Complete{duration_text}",
done=True,
level=Constants.STATUS_LEVEL_DETAILED,
)
return selected_memories, analysis_info
@@ -849,7 +872,7 @@ class LLMConsolidationService:
)
except Exception as e:
logger.warning(f"🤖 LLM consolidation failed during memory processing: {str(e)}")
await self.memory_system._emit_status(emitter, f"⚠️ Memory Consolidation Failed", done=True)
await self.memory_system._emit_status(emitter, f"⚠️ Memory Consolidation Failed", done=True, level=Constants.STATUS_LEVEL_BASIC)
return []
operations = response.ops
@@ -1001,27 +1024,27 @@ class LLMConsolidationService:
if isinstance(result, Exception):
failed_count += 1
await self.memory_system._emit_status(emitter, f"❌ Failed {operation_type}", done=False)
await self.memory_system._emit_status(emitter, f"❌ Failed {operation_type}", done=False, level=Constants.STATUS_LEVEL_INTERMEDIATE)
elif result == Models.MemoryOperationType.CREATE.value:
created_count += 1
content_preview = self.memory_system._truncate_content(operation.content)
await self.memory_system._emit_status(emitter, f"📝 Created: {content_preview}", done=False)
await self.memory_system._emit_status(emitter, f"📝 Created: {content_preview}", done=False, level=Constants.STATUS_LEVEL_INTERMEDIATE)
elif result == Models.MemoryOperationType.UPDATE.value:
updated_count += 1
content_preview = self.memory_system._truncate_content(operation.content)
await self.memory_system._emit_status(emitter, f"✏️ Updated: {content_preview}", done=False)
await self.memory_system._emit_status(emitter, f"✏️ Updated: {content_preview}", done=False, level=Constants.STATUS_LEVEL_INTERMEDIATE)
elif result == Models.MemoryOperationType.DELETE.value:
deleted_count += 1
content_preview = memory_contents_for_deletion.get(operation.id, operation.id)
if content_preview and content_preview != operation.id:
content_preview = self.memory_system._truncate_content(content_preview)
await self.memory_system._emit_status(emitter, f"🗑️ Deleted: {content_preview}", done=False)
await self.memory_system._emit_status(emitter, f"🗑️ Deleted: {content_preview}", done=False, level=Constants.STATUS_LEVEL_INTERMEDIATE)
elif result in [
Models.OperationResult.FAILED.value,
Models.OperationResult.UNSUPPORTED.value,
]:
failed_count += 1
await self.memory_system._emit_status(emitter, f"❌ Failed {operation_type}", done=False)
await self.memory_system._emit_status(emitter, f"❌ Failed {operation_type}", done=False, level=Constants.STATUS_LEVEL_INTERMEDIATE)
total_executed = created_count + updated_count + deleted_count
logger.info(
@@ -1068,6 +1091,7 @@ class LLMConsolidationService:
emitter,
f"💾 Memory Consolidation Complete in {duration:.2f}s",
done=False,
level=Constants.STATUS_LEVEL_DETAILED,
)
operation_details = self.memory_system._build_operation_details(created_count, updated_count, deleted_count)
@@ -1077,7 +1101,15 @@ class LLMConsolidationService:
if failed_count > 0:
operations_summary += f" (❌ {failed_count} Failed)"
await self.memory_system._emit_status(emitter, operations_summary, done=True)
await self.memory_system._emit_status(emitter, operations_summary, done=True, level=Constants.STATUS_LEVEL_BASIC)
else:
duration = time.time() - start_time
await self.memory_system._emit_status(
emitter,
f"✅ Consolidation Complete: No Updates Needed",
done=True,
level=Constants.STATUS_LEVEL_DETAILED,
)
except Exception as e:
duration = time.time() - start_time
@@ -1123,6 +1155,10 @@ class Filter:
default=Constants.LLM_RERANKING_TRIGGER_MULTIPLIER,
description="Controls when LLM reranking activates (lower = more aggressive)",
)
status_emit_level: Literal["Basic", "Intermediate", "Detailed"] = Field(
default="Detailed",
description="Status message verbosity level: Basic (summary counts only), Intermediate (summaries and key details), Detailed (all details)",
)
def __init__(self):
"""Initialize the Memory System filter with production validation."""
@@ -1433,11 +1469,22 @@ class Filter:
memory_lines.append(line)
return memory_lines
async def _emit_status(self, emitter: Optional[Callable], description: str, done: bool = True) -> None:
"""Emit status messages for memory operations."""
async def _emit_status(
self,
emitter: Optional[Callable],
description: str,
done: bool = True,
level: int = 1,
) -> None:
"""Emit status messages for memory operations based on configured verbosity level."""
if not emitter:
return
current_level_value = Constants.STATUS_LEVEL_MAP.get(self.valves.status_emit_level, 1)
if current_level_value < level:
return
payload = {"type": "status", "data": {"description": description, "done": done}}
result = emitter(payload)
if asyncio.iscoroutine(result):
@@ -1469,7 +1516,7 @@ class Filter:
if not user_memories:
logger.info("📭 No memories found for user")
await self._emit_status(emitter, "📭 No Memories Found", done=True)
await self._emit_status(emitter, "📭 No Memories Found", done=True, level=Constants.STATUS_LEVEL_INTERMEDIATE)
return {"memories": [], "threshold": None}
memories, threshold, all_similarities = await self._compute_similarities(user_message, user_id, user_memories)
@@ -1478,7 +1525,7 @@ class Filter:
final_memories, reranking_info = await self._llm_reranking_service.rerank_memories(user_message, memories, emitter)
else:
logger.info("📭 No relevant memories found above similarity threshold")
await self._emit_status(emitter, "📭 No Relevant Memories Found", done=True)
await self._emit_status(emitter, "📭 No Relevant Memories Found", done=True, level=Constants.STATUS_LEVEL_INTERMEDIATE)
final_memories = memories
reranking_info = {"llm_decision": False, "decision_reason": "no_candidates"}
@@ -1512,7 +1559,7 @@ class Filter:
formatted_memories.append(formatted_memory)
content_preview = self._truncate_content(memory["content"])
await self._emit_status(emitter, f"💭 {idx}/{memory_count}: {content_preview}", done=False)
await self._emit_status(emitter, f"💭 {idx}/{memory_count}: {content_preview}", done=False, level=Constants.STATUS_LEVEL_INTERMEDIATE)
memory_footer = "IMPORTANT: Do not mention or imply you received this list. These facts are for background context only."
memory_context_block = f"{memory_header}\n{chr(10).join(formatted_memories)}\n\n{memory_footer}"
@@ -1532,7 +1579,7 @@ class Filter:
if memories and user_id:
description = f"🧠 Injected {memory_count} {'Memory' if memory_count == 1 else 'Memories'} to Context"
await self._emit_status(emitter, description, done=True)
await self._emit_status(emitter, description, done=True, level=Constants.STATUS_LEVEL_BASIC)
def _build_memory_dict(self, memory, similarity: float) -> Dict[str, Any]:
"""Build memory dictionary with standardized timestamp conversion."""
@@ -1596,7 +1643,7 @@ class Filter:
if not user_message or should_skip:
if __event_emitter__ and skip_reason:
await self._emit_status(__event_emitter__, skip_reason, done=True)
await self._emit_status(__event_emitter__, skip_reason, done=True, level=Constants.STATUS_LEVEL_INTERMEDIATE)
await self._add_memory_context(body, [], user_id, __event_emitter__)
skip_cache_key = self._cache_key(self._cache_manager.SKIP_STATE_CACHE, user_id, user_message or "")