Implementation Summary:
- REST API with FastAPI (complete CRUD operations)
- MCP Server with Python MCP SDK (7 tools)
- Supabase migrations (pgvector setup)
- Docker Compose orchestration
- Mintlify documentation site
- Environment configuration
- Shared config module
REST API Features:
- POST /v1/memories/ - Add memory
- GET /v1/memories/search - Semantic search
- GET /v1/memories/{id} - Get memory
- GET /v1/memories/user/{user_id} - User memories
- PATCH /v1/memories/{id} - Update memory
- DELETE /v1/memories/{id} - Delete memory
- GET /v1/health - Health check
- GET /v1/stats - Statistics
- Bearer token authentication
- OpenAPI documentation
MCP Server Tools:
- add_memory - Add from messages
- search_memories - Semantic search
- get_memory - Retrieve by ID
- get_all_memories - List all
- update_memory - Update content
- delete_memory - Delete by ID
- delete_all_memories - Bulk delete
Infrastructure:
- Neo4j 5.26 with APOC/GDS
- Supabase pgvector integration
- Docker network: localai
- Health checks and monitoring
- Structured logging
Documentation:
- Introduction page
- Quickstart guide
- Architecture deep dive
- Mintlify configuration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
354 lines
9.0 KiB
Python
354 lines
9.0 KiB
Python
"""
|
|
Memory service wrapper for Mem0 core library
|
|
"""
|
|
|
|
import logging
|
|
from typing import List, Dict, Any, Optional
|
|
from mem0 import Memory
|
|
from config import mem0_config
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class MemoryService:
|
|
"""Singleton service for memory operations using Mem0"""
|
|
|
|
_instance: Optional['MemoryService'] = None
|
|
_memory: Optional[Memory] = None
|
|
|
|
def __new__(cls):
|
|
if cls._instance is None:
|
|
cls._instance = super().__new__(cls)
|
|
return cls._instance
|
|
|
|
def __init__(self):
|
|
"""Initialize memory service with Mem0"""
|
|
if self._memory is None:
|
|
logger.info("Initializing Mem0 with configuration")
|
|
try:
|
|
self._memory = Memory.from_config(config_dict=mem0_config)
|
|
logger.info("Mem0 initialized successfully")
|
|
except Exception as e:
|
|
logger.error(f"Failed to initialize Mem0: {e}")
|
|
raise
|
|
|
|
@property
|
|
def memory(self) -> Memory:
|
|
"""Get Mem0 instance"""
|
|
if self._memory is None:
|
|
raise RuntimeError("MemoryService not initialized")
|
|
return self._memory
|
|
|
|
async def add_memory(
|
|
self,
|
|
messages: List[Dict[str, str]],
|
|
user_id: Optional[str] = None,
|
|
agent_id: Optional[str] = None,
|
|
run_id: Optional[str] = None,
|
|
metadata: Optional[Dict[str, Any]] = None
|
|
) -> List[Dict[str, Any]]:
|
|
"""
|
|
Add new memory from messages
|
|
|
|
Args:
|
|
messages: List of chat messages
|
|
user_id: User identifier
|
|
agent_id: Agent identifier
|
|
run_id: Run identifier
|
|
metadata: Additional metadata
|
|
|
|
Returns:
|
|
List of created memories
|
|
|
|
Raises:
|
|
Exception: If memory creation fails
|
|
"""
|
|
try:
|
|
logger.info(f"Adding memory for user_id={user_id}, agent_id={agent_id}")
|
|
|
|
result = self.memory.add(
|
|
messages=messages,
|
|
user_id=user_id,
|
|
agent_id=agent_id,
|
|
run_id=run_id,
|
|
metadata=metadata or {}
|
|
)
|
|
|
|
logger.info(f"Successfully added {len(result.get('results', []))} memories")
|
|
return result.get('results', [])
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to add memory: {e}")
|
|
raise
|
|
|
|
async def search_memories(
|
|
self,
|
|
query: str,
|
|
user_id: Optional[str] = None,
|
|
agent_id: Optional[str] = None,
|
|
run_id: Optional[str] = None,
|
|
limit: int = 10
|
|
) -> List[Dict[str, Any]]:
|
|
"""
|
|
Search memories by query
|
|
|
|
Args:
|
|
query: Search query
|
|
user_id: User identifier filter
|
|
agent_id: Agent identifier filter
|
|
run_id: Run identifier filter
|
|
limit: Maximum results
|
|
|
|
Returns:
|
|
List of matching memories with scores
|
|
|
|
Raises:
|
|
Exception: If search fails
|
|
"""
|
|
try:
|
|
logger.info(f"Searching memories: query='{query}', user_id={user_id}, limit={limit}")
|
|
|
|
results = self.memory.search(
|
|
query=query,
|
|
user_id=user_id,
|
|
agent_id=agent_id,
|
|
run_id=run_id,
|
|
limit=limit
|
|
)
|
|
|
|
logger.info(f"Found {len(results)} matching memories")
|
|
return results
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to search memories: {e}")
|
|
raise
|
|
|
|
async def get_memory(
|
|
self,
|
|
memory_id: str
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Get specific memory by ID
|
|
|
|
Args:
|
|
memory_id: Memory identifier
|
|
|
|
Returns:
|
|
Memory data or None if not found
|
|
|
|
Raises:
|
|
Exception: If retrieval fails
|
|
"""
|
|
try:
|
|
logger.info(f"Getting memory: id={memory_id}")
|
|
|
|
result = self.memory.get(memory_id=memory_id)
|
|
|
|
if result:
|
|
logger.info(f"Retrieved memory: {memory_id}")
|
|
else:
|
|
logger.warning(f"Memory not found: {memory_id}")
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get memory: {e}")
|
|
raise
|
|
|
|
async def get_all_memories(
|
|
self,
|
|
user_id: Optional[str] = None,
|
|
agent_id: Optional[str] = None,
|
|
run_id: Optional[str] = None
|
|
) -> List[Dict[str, Any]]:
|
|
"""
|
|
Get all memories for a user/agent/run
|
|
|
|
Args:
|
|
user_id: User identifier filter
|
|
agent_id: Agent identifier filter
|
|
run_id: Run identifier filter
|
|
|
|
Returns:
|
|
List of all matching memories
|
|
|
|
Raises:
|
|
Exception: If retrieval fails
|
|
"""
|
|
try:
|
|
logger.info(f"Getting all memories: user_id={user_id}, agent_id={agent_id}")
|
|
|
|
results = self.memory.get_all(
|
|
user_id=user_id,
|
|
agent_id=agent_id,
|
|
run_id=run_id
|
|
)
|
|
|
|
logger.info(f"Retrieved {len(results)} memories")
|
|
return results
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get all memories: {e}")
|
|
raise
|
|
|
|
async def update_memory(
|
|
self,
|
|
memory_id: str,
|
|
data: str
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Update existing memory
|
|
|
|
Args:
|
|
memory_id: Memory identifier
|
|
data: Updated memory text
|
|
|
|
Returns:
|
|
Updated memory data
|
|
|
|
Raises:
|
|
Exception: If update fails
|
|
"""
|
|
try:
|
|
logger.info(f"Updating memory: id={memory_id}")
|
|
|
|
result = self.memory.update(
|
|
memory_id=memory_id,
|
|
data=data
|
|
)
|
|
|
|
logger.info(f"Successfully updated memory: {memory_id}")
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to update memory: {e}")
|
|
raise
|
|
|
|
async def delete_memory(
|
|
self,
|
|
memory_id: str
|
|
) -> bool:
|
|
"""
|
|
Delete memory by ID
|
|
|
|
Args:
|
|
memory_id: Memory identifier
|
|
|
|
Returns:
|
|
True if deleted successfully
|
|
|
|
Raises:
|
|
Exception: If deletion fails
|
|
"""
|
|
try:
|
|
logger.info(f"Deleting memory: id={memory_id}")
|
|
|
|
self.memory.delete(memory_id=memory_id)
|
|
|
|
logger.info(f"Successfully deleted memory: {memory_id}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to delete memory: {e}")
|
|
raise
|
|
|
|
async def delete_all_memories(
|
|
self,
|
|
user_id: Optional[str] = None,
|
|
agent_id: Optional[str] = None,
|
|
run_id: Optional[str] = None
|
|
) -> bool:
|
|
"""
|
|
Delete all memories for a user/agent/run
|
|
|
|
Args:
|
|
user_id: User identifier filter
|
|
agent_id: Agent identifier filter
|
|
run_id: Run identifier filter
|
|
|
|
Returns:
|
|
True if deleted successfully
|
|
|
|
Raises:
|
|
Exception: If deletion fails
|
|
"""
|
|
try:
|
|
logger.info(f"Deleting all memories: user_id={user_id}, agent_id={agent_id}")
|
|
|
|
self.memory.delete_all(
|
|
user_id=user_id,
|
|
agent_id=agent_id,
|
|
run_id=run_id
|
|
)
|
|
|
|
logger.info("Successfully deleted all matching memories")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to delete all memories: {e}")
|
|
raise
|
|
|
|
async def get_memory_history(
|
|
self,
|
|
memory_id: str
|
|
) -> List[Dict[str, Any]]:
|
|
"""
|
|
Get history of a memory
|
|
|
|
Args:
|
|
memory_id: Memory identifier
|
|
|
|
Returns:
|
|
List of memory history entries
|
|
|
|
Raises:
|
|
Exception: If retrieval fails
|
|
"""
|
|
try:
|
|
logger.info(f"Getting memory history: id={memory_id}")
|
|
|
|
result = self.memory.history(memory_id=memory_id)
|
|
|
|
logger.info(f"Retrieved history for memory: {memory_id}")
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get memory history: {e}")
|
|
raise
|
|
|
|
async def health_check(self) -> Dict[str, str]:
|
|
"""
|
|
Check health of memory service
|
|
|
|
Returns:
|
|
Dict with component health status
|
|
"""
|
|
health = {}
|
|
|
|
try:
|
|
# Test Mem0 access
|
|
self.memory
|
|
health['mem0'] = 'healthy'
|
|
except Exception as e:
|
|
logger.error(f"Mem0 health check failed: {e}")
|
|
health['mem0'] = 'unhealthy'
|
|
|
|
return health
|
|
|
|
|
|
# Global service instance
|
|
_memory_service: Optional[MemoryService] = None
|
|
|
|
|
|
def get_memory_service() -> MemoryService:
|
|
"""
|
|
Get or create memory service singleton
|
|
|
|
Returns:
|
|
MemoryService instance
|
|
"""
|
|
global _memory_service
|
|
if _memory_service is None:
|
|
_memory_service = MemoryService()
|
|
return _memory_service
|