8.2 Memory System: Giving Agents Memory
8.2.1 Memory System Workflow
Before entering the code implementation phase, we need to first define the workflow of the memory system. This workflow references the memory model in cognitive science and maps each cognitive stage to specific technical components and operations. Understanding this mapping relationship will help us with subsequent code implementation.

Figure 8.3 Cognitive Process of Memory Formation
As shown in Figure 8.3, according to cognitive science research, the formation of human memory goes through the following stages:
- Encoding: Converting perceived information into a storable form
- Storage: Saving encoded information in the memory system
- Retrieval: Extracting relevant information from memory as needed
- Consolidation: Converting short-term memory into long-term memory
- Forgetting: Deleting unimportant or outdated information
Based on this inspiration, we designed a complete memory system for HelloAgents. Its core idea is to mimic how the human brain processes different types of information, dividing memory into multiple specialized modules and establishing an intelligent management mechanism. Figure 8.4 shows in detail the workflow of this system, including key links such as memory addition, retrieval, consolidation, and forgetting.

Figure 8.4 Complete Workflow of HelloAgents Memory System
Our memory system consists of four different types of memory modules, each optimized for specific application scenarios and lifecycles:
First is Working Memory, which plays the role of the agent's "short-term memory," mainly used to store context information of the current conversation. To ensure high-speed access and response, its capacity is intentionally limited (for example, 50 items by default), and its lifecycle is bound to a single session, automatically clearing after the session ends.
Second is Episodic Memory, which is responsible for long-term storage of specific interaction events and the agent's learning experiences. Unlike working memory, episodic memory contains rich contextual information and supports retrospective retrieval by time series or topic, serving as the foundation for the agent to "review" and learn from past experiences.
Corresponding to specific events is Semantic Memory, which stores more abstract knowledge, concepts, and rules. For example, user preferences learned through conversations, instructions that need to be followed long-term, or domain knowledge points are all suitable for storage here. This part of memory has high persistence and importance and is the core for the agent to form a "knowledge system" and perform associative reasoning.
Finally, to interact with increasingly rich multimedia, we introduced Perceptual Memory. This module specifically handles multimodal information such as images and audio and supports cross-modal retrieval. Its lifecycle is dynamically managed based on the importance of information and available storage space.
8.2.2 Quick Experience: Get Started with Memory Features in 30 Seconds
Before diving into implementation details, let's quickly experience the basic functions of the memory system:
from hello_agents import SimpleAgent, HelloAgentsLLM, ToolRegistry
from hello_agents.tools import MemoryTool
# Create LLM instance
llm = HelloAgentsLLM()
# Create memory tool
memory_tool = MemoryTool(user_id="user123")
tool_registry = ToolRegistry()
tool_registry.register_tool(memory_tool)
# Create Agent with memory capability
agent = SimpleAgent(name="Memory Assistant", llm=llm, tool_registry=tool_registry)
# Experience memory features
print("=== Adding Multiple Memories ===")
# Add first memory
result1 = memory_tool.run("add", content="User Zhang San is a Python developer focusing on machine learning and data analysis", memory_type="semantic", importance=0.8)
print(f"Memory 1: {result1}")
# Add second memory
result2 = memory_tool.run("add", content="Li Si is a frontend engineer skilled in React and Vue.js development", memory_type="semantic", importance=0.7)
print(f"Memory 2: {result2}")
# Add third memory
result3 = memory_tool.run("add", content="Wang Wu is a product manager responsible for user experience design and requirements analysis", memory_type="semantic", importance=0.6)
print(f"Memory 3: {result3}")
print("\n=== Searching Specific Memories ===")
# Search for frontend-related memories
print("🔍 Searching 'frontend engineer':")
result = memory_tool.run("search", query="frontend engineer", limit=3)
print(result)
print("\n=== Memory Summary ===")
result = memory_tool.run("summary")
print(result)8.2.3 MemoryTool Detailed Explanation
Now let's adopt a top-down approach, starting from the specific operations supported by MemoryTool and gradually delving into the underlying implementation. MemoryTool, as the unified interface of the memory system, follows the architectural pattern of "unified entry, distributed processing":
def execute(self, action: str, **kwargs) -> str:
"""Execute memory operation
Supported operations:
- add: Add memory (supports 4 types: working/episodic/semantic/perceptual)
- search: Search memory
- summary: Get memory summary
- stats: Get statistics
- update: Update memory
- remove: Delete memory
- forget: Forget memory (multiple strategies)
- consolidate: Consolidate memory (short-term → long-term)
- clear_all: Clear all memories
"""
if action == "add":
return self._add_memory(**kwargs)
elif action == "search":
return self._search_memory(**kwargs)
elif action == "summary":
return self._get_summary(**kwargs)
# ... other operationsThis unified execute interface design simplifies the Agent's calling method. The specific operation is specified through the action parameter, and **kwargs allows each operation to have different parameter requirements. Here we will list several important operations:
(1) Operation 1: add
The add operation is the foundation of the memory system. It simulates the process of the human brain encoding perceived information into memory. In implementation, we not only need to store memory content but also add rich contextual information to each memory. This information will play an important role in subsequent retrieval and management.
def _add_memory(
self,
content: str = "",
memory_type: str = "working",
importance: float = 0.5,
file_path: str = None,
modality: str = None,
**metadata
) -> str:
"""Add memory"""
try:
# Ensure session ID exists
if self.current_session_id is None:
self.current_session_id = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
# Perceptual memory file support
if memory_type == "perceptual" and file_path:
inferred = modality or self._infer_modality(file_path)
metadata.setdefault("modality", inferred)
metadata.setdefault("raw_data", file_path)
# Add session information to metadata
metadata.update({
"session_id": self.current_session_id,
"timestamp": datetime.now().isoformat()
})
memory_id = self.memory_manager.add_memory(
content=content,
memory_type=memory_type,
importance=importance,
metadata=metadata,
auto_classify=False
)
return f"✅ Memory added (ID: {memory_id[:8]}...)"
except Exception as e:
return f"❌ Failed to add memory: {str(e)}"This mainly implements three key tasks: automatic management of session IDs (ensuring each memory has a clear session attribution), intelligent processing of multimodal data (automatically inferring file types and saving related metadata), and automatic supplementation of contextual information (adding timestamps and session information to each memory). Among them, the importance parameter (default 0.5) is used to mark the importance level of memory, with a value range of 0.0-1.0. This mechanism simulates the human brain's assessment of the importance of different information. This design allows the Agent to automatically distinguish conversations from different time periods and provide rich contextual information for subsequent retrieval and management.
For each memory type, we provide different usage examples:
# 1. Working Memory - Temporary information, limited capacity
memory_tool.run("add",
content="User just asked a question about Python functions",
memory_type="working",
importance=0.6
)
# 2. Episodic Memory - Specific events and experiences
memory_tool.run("add",
content="On March 15, 2024, user Zhang San completed their first Python project",
memory_type="episodic",
importance=0.8,
event_type="milestone",
location="Online learning platform"
)
# 3. Semantic Memory - Abstract knowledge and concepts
memory_tool.run("add",
content="Python is an interpreted, object-oriented programming language",
memory_type="semantic",
importance=0.9,
knowledge_type="factual"
)
# 4. Perceptual Memory - Multimodal information
memory_tool.run("add",
content="User uploaded a Python code screenshot containing function definitions",
memory_type="perceptual",
importance=0.7,
modality="image",
file_path="./uploads/code_screenshot.png"
)(2) Operation 2: search
The search operation is the core function of the memory system. It needs to quickly find the most relevant content to the query among a large number of memories. It involves multiple steps such as semantic understanding, relevance calculation, and result sorting.
def _search_memory(
self,
query: str,
limit: int = 5,
memory_types: List[str] = None,
memory_type: str = None,
min_importance: float = 0.1
) -> str:
"""Search memory"""
try:
# Parameter standardization
if memory_type and not memory_types:
memory_types = [memory_type]
results = self.memory_manager.retrieve_memories(
query=query,
limit=limit,
memory_types=memory_types,
min_importance=min_importance
)
if not results:
return f"🔍 No memories found related to '{query}'"
# Format results
formatted_results = []
formatted_results.append(f"🔍 Found {len(results)} related memories:")
for i, memory in enumerate(results, 1):
memory_type_label = {
"working": "Working Memory",
"episodic": "Episodic Memory",
"semantic": "Semantic Memory",
"perceptual": "Perceptual Memory"
}.get(memory.memory_type, memory.memory_type)
content_preview = memory.content[:80] + "..." if len(memory.content) > 80 else memory.content
formatted_results.append(
f"{i}. [{memory_type_label}] {content_preview} (Importance: {memory.importance:.2f})"
)
return "\n".join(formatted_results)
except Exception as e:
return f"❌ Failed to search memory: {str(e)}"The search operation is designed to support both singular and plural parameter forms (memory_type and memory_types), allowing users to express their needs in the most natural way. Among them, the min_importance parameter (default 0.1) is used to filter low-quality memories. For the use of the search function, you can refer to this example:
# Basic search
result = memory_tool.execute("search", query="Python programming", limit=5)
# Search by specifying memory type
result = memory_tool.execute("search",
query="learning progress",
memory_type="episodic",
limit=3
)
# Multi-type search
result = memory_tool.execute("search",
query="function definition",
memory_types=["semantic", "episodic"],
min_importance=0.5
)(3) Operation 3: forget
The forgetting mechanism is the most cognitively scientific feature. It simulates the human brain's selective forgetting process and supports three strategies: importance-based (deleting unimportant memories), time-based (deleting outdated memories), and capacity-based (deleting the least important memories when storage approaches the limit).
def _forget(self, strategy: str = "importance_based", threshold: float = 0.1, max_age_days: int = 30) -> str:
"""Forget memories (supports multiple strategies)"""
try:
count = self.memory_manager.forget_memories(
strategy=strategy,
threshold=threshold,
max_age_days=max_age_days
)
return f"🧹 Forgot {count} memories (strategy: {strategy})"
except Exception as e:
return f"❌ Failed to forget memories: {str(e)}"Usage of three forgetting strategies:
# 1. Importance-based forgetting - Delete memories below importance threshold
memory_tool.execute("forget",
strategy="importance_based",
threshold=0.2
)
# 2. Time-based forgetting - Delete memories older than specified days
memory_tool.execute("forget",
strategy="time_based",
max_age_days=30
)
# 3. Capacity-based forgetting - Delete least important when memory count exceeds limit
memory_tool.execute("forget",
strategy="capacity_based",
threshold=0.3
)(4) Operation 4: consolidate
def _consolidate(self, from_type: str = "working", to_type: str = "episodic", importance_threshold: float = 0.7) -> str:
"""Consolidate memories (promote important short-term memories to long-term memories)"""
try:
count = self.memory_manager.consolidate_memories(
from_type=from_type,
to_type=to_type,
importance_threshold=importance_threshold,
)
return f"🔄 Consolidated {count} memories to long-term memory ({from_type} → {to_type}, threshold={importance_threshold})"
except Exception as e:
return f"❌ Failed to consolidate memories: {str(e)}"The consolidate operation draws on the concept of memory consolidation in neuroscience, simulating the process of the human brain converting short-term memory into long-term memory. The default setting is to convert working memories with importance exceeding 0.7 into episodic memories. This threshold ensures that only truly important information is preserved long-term. The entire process is automated; users do not need to manually select specific memories. The system intelligently identifies memories that meet the criteria and performs type conversion.
Usage examples of memory consolidation:
# Convert important working memories to episodic memories
memory_tool.execute("consolidate",
from_type="working",
to_type="episodic",
importance_threshold=0.7
)
# Convert important episodic memories to semantic memories
memory_tool.execute("consolidate",
from_type="episodic",
to_type="semantic",
importance_threshold=0.8
)Through the collaboration of these core operations, MemoryTool builds a complete memory lifecycle management system. From memory creation, retrieval, summarization to forgetting, consolidation, and management, it forms a closed-loop intelligent memory management system, giving the Agent truly human-like memory capabilities.
8.2.4 MemoryManager Detailed Explanation
After understanding the interface design of MemoryTool, let's delve into the underlying implementation to see how MemoryTool collaborates with MemoryManager. This layered design embodies the separation of concerns principle in software engineering. MemoryTool focuses on user interface and parameter processing, while MemoryManager is responsible for core memory management logic.
MemoryTool creates a MemoryManager instance during initialization and enables different types of memory modules based on configuration. This design allows users to choose which memory types to enable based on specific needs, ensuring functional completeness while avoiding unnecessary resource consumption.
class MemoryTool(Tool):
"""Memory tool - Provides memory functionality for Agent"""
def __init__(
self,
user_id: str = "default_user",
memory_config: MemoryConfig = None,
memory_types: List[str] = None
):
super().__init__(
name="memory",
description="Memory tool - Can store and retrieve conversation history, knowledge, and experience"
)
# Initialize memory manager
self.memory_config = memory_config or MemoryConfig()
self.memory_types = memory_types or ["working", "episodic", "semantic"]
self.memory_manager = MemoryManager(
config=self.memory_config,
user_id=user_id,
enable_working="working" in self.memory_types,
enable_episodic="episodic" in self.memory_types,
enable_semantic="semantic" in self.memory_types,
enable_perceptual="perceptual" in self.memory_types
)MemoryManager, as the core coordinator of the memory system, is responsible for managing different types of memory modules and providing a unified operation interface. The concrete storage and retrieval capabilities are implemented internally by each memory type.
class MemoryManager:
"""Memory manager - Unified memory operation interface"""
def __init__(
self,
config: Optional[MemoryConfig] = None,
user_id: str = "default_user",
enable_working: bool = True,
enable_episodic: bool = True,
enable_semantic: bool = True,
enable_perceptual: bool = False
):
self.config = config or MemoryConfig()
self.user_id = user_id
# Storage and retrieval are implemented within each memory type
# Initialize various types of memory
self.memory_types = {}
if enable_working:
self.memory_types['working'] = WorkingMemory(self.config)
if enable_episodic:
self.memory_types['episodic'] = EpisodicMemory(self.config)
if enable_semantic:
self.memory_types['semantic'] = SemanticMemory(self.config)
if enable_perceptual:
self.memory_types['perceptual'] = PerceptualMemory(self.config)8.2.5 Four Types of Memory
Now let's delve into the specific implementation of the four memory types. Each memory type has its unique characteristics and application scenarios:
(1) Working Memory
Working memory is the most active part of the memory system. It is responsible for storing temporary information in the current conversation session. The design focus of working memory is on fast access and automatic cleanup, which ensures the system's response speed and resource efficiency.
Working memory adopts a pure in-memory storage solution, combined with a TTL (Time To Live) mechanism for automatic cleanup. The advantage of this design is extremely fast access speed, but it also means that the content of working memory will be lost after system restart. This characteristic perfectly fits the positioning of working memory: storing temporary and volatile information.
class WorkingMemory:
"""Working memory implementation
Features:
- Limited capacity (default 50 items) + TTL automatic cleanup
- Pure in-memory storage, extremely fast access
- Combined lexical retrieval: TF-IDF term-weight similarity + keyword matching
"""
def __init__(self, config: MemoryConfig):
self.max_capacity = config.working_memory_capacity or 50
self.max_age_minutes = config.working_memory_ttl or 60
self.memories = []
def add(self, memory_item: MemoryItem) -> str:
"""Add working memory"""
self._expire_old_memories() # Expiration cleanup
if len(self.memories) >= self.max_capacity:
self._remove_lowest_priority_memory() # Capacity management
self.memories.append(memory_item)
return memory_item.id
def retrieve(self, query: str, limit: int = 5, **kwargs) -> List[MemoryItem]:
"""Combined lexical retrieval: TF-IDF term-weight similarity + keyword matching"""
self._expire_old_memories()
# Calculate TF-IDF term-weight similarity
vector_scores = self._try_tfidf_search(query)
# Calculate comprehensive score
scored_memories = []
for memory in self.memories:
vector_score = vector_scores.get(memory.id, 0.0)
keyword_score = self._calculate_keyword_score(query, memory.content)
# Hybrid scoring
base_relevance = vector_score * 0.7 + keyword_score * 0.3 if vector_score > 0 else keyword_score
time_decay = self._calculate_time_decay(memory.timestamp)
importance_weight = 0.8 + (memory.importance * 0.4)
final_score = base_relevance * time_decay * importance_weight
if final_score > 0:
scored_memories.append((final_score, memory))
scored_memories.sort(key=lambda x: x[0], reverse=True)
return [memory for _, memory in scored_memories[:limit]]Working memory uses a combined lexical retrieval strategy: TF-IDF represents text as sparse term vectors and calculates lexical similarity, while keyword matching provides an additional signal. When TF-IDF is unavailable or produces no effective score, the keyword score is used instead. TF-IDF vectorization is based on term frequency and inverse document frequency; it is not equivalent to semantic retrieval based on dense embeddings. The scoring algorithm combines lexical relevance, time decay, and importance weight. The final score formula is: (relevance × time decay) × (0.8 + importance × 0.4).
(2) Episodic Memory
Episodic memory is responsible for storing specific events and experiences. Its design focus is on maintaining the integrity of events and temporal sequence relationships. Episodic memory adopts a hybrid storage solution of SQLite + Qdrant. SQLite is responsible for storing structured data and complex queries, while Qdrant is responsible for efficient vector retrieval.
class EpisodicMemory:
"""Episodic memory implementation
Features:
- SQLite+Qdrant hybrid storage architecture
- Supports time series and session-level retrieval
- Structured filtering + semantic vector retrieval
"""
def __init__(self, config: MemoryConfig):
self.doc_store = SQLiteDocumentStore(config.database_path)
self.vector_store = QdrantVectorStore(config.qdrant_url, config.qdrant_api_key)
self.embedder = create_embedding_model_with_fallback()
self.sessions = {} # Session index
def add(self, memory_item: MemoryItem) -> str:
"""Add episodic memory"""
# Create episode object
episode = Episode(
episode_id=memory_item.id,
session_id=memory_item.metadata.get("session_id", "default"),
timestamp=memory_item.timestamp,
content=memory_item.content,
context=memory_item.metadata
)
# Update session index
session_id = episode.session_id
if session_id not in self.sessions:
self.sessions[session_id] = []
self.sessions[session_id].append(episode.episode_id)
# Persistent storage (SQLite + Qdrant)
self._persist_episode(episode)
return memory_item.id
def retrieve(self, query: str, limit: int = 5, **kwargs) -> List[MemoryItem]:
"""Hybrid retrieval: structured filtering + semantic vector retrieval"""
# 1. Structured pre-filtering (time range, importance, etc.)
candidate_ids = self._structured_filter(**kwargs)
# 2. Vector semantic retrieval
hits = self._vector_search(query, limit * 5, kwargs.get("user_id"))
# 3. Comprehensive scoring and sorting
results = []
for hit in hits:
if self._should_include(hit, candidate_ids, kwargs):
score = self._calculate_episode_score(hit)
memory_item = self._create_memory_item(hit)
results.append((score, memory_item))
results.sort(key=lambda x: x[0], reverse=True)
return [item for _, item in results[:limit]]
def _calculate_episode_score(self, hit) -> float:
"""Episodic memory scoring algorithm"""
vec_score = float(hit.get("score", 0.0))
recency_score = self._calculate_recency(hit["metadata"]["timestamp"])
importance = hit["metadata"].get("importance", 0.5)
# Scoring formula: (vector similarity × 0.8 + temporal recency × 0.2) × importance weight
base_relevance = vec_score * 0.8 + recency_score * 0.2
importance_weight = 0.8 + (importance * 0.4)
return base_relevance * importance_weightThe retrieval implementation of episodic memory demonstrates a complex multi-factor scoring mechanism. It not only considers semantic similarity but also incorporates temporal recency considerations, ultimately adjusted by importance weight. The scoring formula is: (vector similarity × 0.8 + temporal recency × 0.2) × (0.8 + importance × 0.4), ensuring that retrieval results are both semantically and temporally relevant.
(3) Semantic Memory
Semantic memory is the most complex part of the memory system. It is responsible for storing abstract concepts, rules, and knowledge. The design focus of semantic memory is on structured representation of knowledge and intelligent reasoning capabilities. Semantic memory adopts a hybrid architecture of Neo4j graph database and Qdrant vector database. This design allows the system to perform both fast semantic retrieval and complex relational reasoning using knowledge graphs.
class SemanticMemory(BaseMemory):
"""Semantic memory implementation
Features:
- Uses HuggingFace Chinese pre-trained models for text embedding
- Vector retrieval for fast similarity matching
- Knowledge graph storage for entities and relationships
- Hybrid retrieval strategy: vector + graph + semantic reasoning
"""
def __init__(self, config: MemoryConfig, storage_backend=None):
super().__init__(config, storage_backend)
# Embedding model (unified provision)
self.embedding_model = get_text_embedder()
# Professional database storage
self.vector_store = QdrantConnectionManager.get_instance(**qdrant_config)
self.graph_store = Neo4jGraphStore(**neo4j_config)
# Entity and relation cache
self.entities: Dict[str, Entity] = {}
self.relations: List[Relation] = []
# NLP processor (supports Chinese and English)
self.nlp = self._init_nlp()The addition process of semantic memory embodies the complete workflow of knowledge graph construction. The system not only stores memory content but also automatically extracts entities and relationships to build structured knowledge representations:
def add(self, memory_item: MemoryItem) -> str:
"""Add semantic memory"""
# 1. Generate text embedding
embedding = self.embedding_model.encode(memory_item.content)
# 2. Extract entities and relations
entities = self._extract_entities(memory_item.content)
relations = self._extract_relations(memory_item.content, entities)
# 3. Store to Neo4j graph database
for entity in entities:
self._add_entity_to_graph(entity, memory_item)
for relation in relations:
self._add_relation_to_graph(relation, memory_item)
# 4. Store to Qdrant vector database
metadata = {
"memory_id": memory_item.id,
"entities": [e.entity_id for e in entities],
"entity_count": len(entities),
"relation_count": len(relations)
}
self.vector_store.add_vectors(
vectors=[embedding.tolist()],
metadata=[metadata],
ids=[memory_item.id]
)The retrieval of semantic memory implements a hybrid search strategy, combining the semantic understanding capability of vector retrieval and the relational reasoning capability of graph retrieval:
def retrieve(self, query: str, limit: int = 5, **kwargs) -> List[MemoryItem]:
"""Retrieve semantic memory"""
# 1. Vector retrieval
vector_results = self._vector_search(query, limit * 2, user_id)
# 2. Graph retrieval
graph_results = self._graph_search(query, limit * 2, user_id)
# 3. Hybrid ranking
combined_results = self._combine_and_rank_results(
vector_results, graph_results, query, limit
)
return combined_results[:limit]The hybrid ranking algorithm adopts a multi-factor scoring mechanism:
def _combine_and_rank_results(self, vector_results, graph_results, query, limit):
"""Hybrid ranking of results"""
combined = {}
# Merge vector and graph retrieval results
for result in vector_results:
combined[result["memory_id"]] = {
**result,
"vector_score": result.get("score", 0.0),
"graph_score": 0.0
}
for result in graph_results:
memory_id = result["memory_id"]
if memory_id in combined:
combined[memory_id]["graph_score"] = result.get("similarity", 0.0)
else:
combined[memory_id] = {
**result,
"vector_score": 0.0,
"graph_score": result.get("similarity", 0.0)
}
# Calculate hybrid score
for memory_id, result in combined.items():
vector_score = result["vector_score"]
graph_score = result["graph_score"]
importance = result.get("importance", 0.5)
# Base relevance score
base_relevance = vector_score * 0.7 + graph_score * 0.3
# Importance weight [0.8, 1.2]
importance_weight = 0.8 + (importance * 0.4)
# Final score: similarity * importance weight
combined_score = base_relevance * importance_weight
result["combined_score"] = combined_score
# Sort and return
sorted_results = sorted(
combined.values(),
key=lambda x: x["combined_score"],
reverse=True
)
return sorted_results[:limit]The scoring formula for semantic memory is: (vector similarity × 0.7 + graph similarity × 0.3) × (0.8 + importance × 0.4). The core idea of this design is:
- Vector retrieval weight (0.7): Semantic similarity is the main factor, ensuring retrieval results are semantically related to the query
- Graph retrieval weight (0.3): Relational reasoning as a supplement, discovering implicit associations between concepts
- Importance weight range [0.8, 1.2]: Avoids excessive influence of importance on similarity ranking, maintaining retrieval accuracy
(4) Perceptual Memory
Perceptual memory supports storage and retrieval of data in multiple modalities such as text, images, and audio. It adopts a modality-separated storage strategy, creating independent vector collections for data of different modalities. This design avoids dimension mismatch problems while ensuring retrieval accuracy:
class PerceptualMemory(BaseMemory):
"""Perceptual memory implementation
Features:
- Supports multimodal data (text, images, audio, etc.)
- Cross-modal similarity search
- Semantic understanding of perceptual data
- Supports content generation and retrieval
"""
def __init__(self, config: MemoryConfig, storage_backend=None):
super().__init__(config, storage_backend)
# Multimodal encoders
self.text_embedder = get_text_embedder()
self._clip_model = self._init_clip_model() # Image encoding
self._clap_model = self._init_clap_model() # Audio encoding
# Modality-separated vector storage
self.vector_stores = {
"text": QdrantConnectionManager.get_instance(
collection_name="perceptual_text",
vector_size=self.vector_dim
),
"image": QdrantConnectionManager.get_instance(
collection_name="perceptual_image",
vector_size=self._image_dim
),
"audio": QdrantConnectionManager.get_instance(
collection_name="perceptual_audio",
vector_size=self._audio_dim
)
}Perceptual memory retrieval supports both same-modality and cross-modality modes. Same-modality retrieval uses specialized encoders for precise matching, while cross-modality retrieval requires more complex semantic alignment mechanisms:
def retrieve(self, query: str, limit: int = 5, **kwargs) -> List[MemoryItem]:
"""Retrieve perceptual memory (can filter modality; same-modality vector retrieval + time/importance fusion)"""
user_id = kwargs.get("user_id")
target_modality = kwargs.get("target_modality")
query_modality = kwargs.get("query_modality", target_modality or "text")
# Same-modality vector retrieval
try:
query_vector = self._encode_data(query, query_modality)
store = self._get_vector_store_for_modality(target_modality or query_modality)
where = {"memory_type": "perceptual"}
if user_id:
where["user_id"] = user_id
if target_modality:
where["modality"] = target_modality
hits = store.search_similar(
query_vector=query_vector,
limit=max(limit * 5, 20),
where=where
)
except Exception:
hits = []
# Fusion ranking (vector similarity + temporal recency + importance weight)
results = []
for hit in hits:
vector_score = float(hit.get("score", 0.0))
recency_score = self._calculate_recency_score(hit["metadata"]["timestamp"])
importance = hit["metadata"].get("importance", 0.5)
# Scoring algorithm
base_relevance = vector_score * 0.8 + recency_score * 0.2
importance_weight = 0.8 + (importance * 0.4)
combined_score = base_relevance * importance_weight
results.append((combined_score, self._create_memory_item(hit)))
results.sort(key=lambda x: x[0], reverse=True)
return [item for _, item in results[:limit]]The scoring formula for perceptual memory is: (vector similarity × 0.8 + temporal recency × 0.2) × (0.8 + importance × 0.4). The scoring mechanism of perceptual memory also supports cross-modal retrieval, achieving semantic alignment of different modality data such as text, images, and audio through a unified vector space. When performing cross-modal retrieval, the system automatically adjusts scoring weights to ensure diversity and accuracy of retrieval results. Additionally, the temporal recency calculation in perceptual memory adopts an exponential decay model:
def _calculate_recency_score(self, timestamp: str) -> float:
"""Calculate temporal recency score"""
try:
memory_time = datetime.fromisoformat(timestamp)
current_time = datetime.now()
age_hours = (current_time - memory_time).total_seconds() / 3600
# Exponential decay: maintain high score within 24 hours, then gradually decay
decay_factor = 0.1 # Decay coefficient
recency_score = math.exp(-decay_factor * age_hours / 24)
return max(0.1, recency_score) # Maintain minimum base score of 0.1
except Exception:
return 0.5 # Default medium scoreThis time decay model simulates the forgetting curve in human memory, ensuring that the perceptual memory system can prioritize retrieval of temporally more relevant memory content.