Initial commit: Production-ready Mem0 interface with monitoring

- Complete Mem0 OSS integration with hybrid datastore
- PostgreSQL + pgvector for vector storage
- Neo4j 5.18 for graph relationships
- Google Gemini embeddings integration
- Comprehensive monitoring with correlation IDs
- Real-time statistics and performance tracking
- Production-grade observability features
- Clean repository with no exposed secrets
This commit is contained in:
Pratik Narola 2025-08-10 17:34:41 +05:30
commit 7689409950
24 changed files with 4876 additions and 0 deletions

39
.env.example Normal file
View file

@ -0,0 +1,39 @@
# API Configuration - UPDATE THESE WITH YOUR VALUES
OPENAI_API_KEY=sk-your-openai-api-key-here
OPENAI_COMPAT_API_KEY=sk-your-openai-compatible-api-key-here
OPENAI_BASE_URL=https://your-openai-compatible-endpoint.com/v1
EMBEDDER_API_KEY=AIzaSy-your-google-gemini-api-key-here
# Database Configuration
POSTGRES_DB=mem0_db
POSTGRES_USER=mem0_user
POSTGRES_PASSWORD=mem0_password
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
# Neo4j Configuration
NEO4J_AUTH=neo4j/mem0_neo4j_password
NEO4J_URI=bolt://neo4j:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=mem0_neo4j_password
# Application Configuration
BACKEND_PORT=8000
FRONTEND_PORT=3000
LOG_LEVEL=INFO
CORS_ORIGINS=http://localhost:3000
# Model Configuration - Intelligent Routing
# These models are automatically selected based on task complexity
DEFAULT_MODEL=claude-sonnet-4 # General purpose model
EXTRACTION_MODEL=claude-sonnet-4 # Memory extraction and processing
FAST_MODEL=o4-mini # Simple/quick responses
ANALYTICAL_MODEL=gemini-2.5-pro # Analysis and comparison tasks
REASONING_MODEL=claude-sonnet-4 # Complex reasoning tasks
EXPERT_MODEL=o3 # Expert-level comprehensive analysis
# IMPORTANT NOTES:
# - Ensure all models are available on your OpenAI-compatible endpoint
# - Verify model availability: curl -H "Authorization: Bearer $API_KEY" $BASE_URL/v1/models
# - Neo4j must be version 5.18+ for vector.similarity.cosine() function
# - Ollama must be running locally with nomic-embed-text:latest model

66
.gitignore vendored Normal file
View file

@ -0,0 +1,66 @@
# Environment files
.env
.env.local
.env.*.local
# Security - API keys and secrets
*.key
*.pem
secrets/
config/secrets/
.secrets
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual environments
venv/
env/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Docker
.dockerignore
# Logs
*.log
logs/
# Database
*.db
*.sqlite3
# Node modules (for any frontend)
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

228
MEM0.md Normal file
View file

@ -0,0 +1,228 @@
# Mem0 Native Capabilities Analysis & Refactoring Plan
## Executive Summary
Mem0 (37.8k GitHub stars) provides comprehensive memory management capabilities out-of-the-box. Our current implementation duplicates significant functionality that Mem0 already handles better. This document outlines what Mem0 provides natively vs our custom implementations, and presents a refactoring plan to leverage Mem0's proven capabilities.
## Research Findings (August 2025)
### Mem0's Proven Performance
- **+26% Accuracy** vs OpenAI Memory (LOCOMO benchmark)
- **91% Faster** responses than full-context
- **90% Lower Token Usage** than full-context
- **37.8k GitHub stars** with active development
### What Mem0 Provides Natively
#### ✅ **Core Memory Operations**
- **Memory Extraction**: Automatically extracts key information from conversations
- **Vector Search**: Semantic similarity search with configurable thresholds
- **User Isolation**: Built-in user_id based memory separation
- **Memory CRUD**: Add, search, update, delete with full lifecycle management
- **Memory History**: Tracks memory evolution and changes over time
#### ✅ **Advanced Intelligence Features**
- **Conflict Resolution**: Built-in logic to handle contradictory information
- **Temporal Awareness**: Memory decay and recency weighting
- **Graph Relationships**: Neo4j integration with automatic relationship extraction
- **Multi-Level Memory**: User, Session, Agent, Run level memory management
- **Categorization**: Native custom categories support
#### ✅ **Enterprise Features**
- **Custom Categories**: Project-level category management
- **Custom Instructions**: Project-specific memory handling instructions
- **Advanced Filtering**: Complex query filters with AND/OR logic
- **Metadata Management**: Rich tagging and filtering capabilities
- **Organizations & Projects**: Multi-tenant architecture support
#### ✅ **Integration Capabilities**
- **Multiple LLMs**: OpenAI, Anthropic, Google, local models
- **Vector Databases**: Multiple backend support
- **Graph Databases**: Neo4j integration
- **Custom Endpoints**: OpenAI-compatible endpoint support
## Current Implementation Analysis
### File: `mem0_manager.py` (474 lines)
#### 🔴 **Logic We Should Remove (Duplicates Mem0)**
**Lines 57-92: Task Complexity Analysis**
```python
def analyze_task_complexity(self, query: str, context: Optional[List[ChatMessage]] = None) -> TaskMetrics:
```
- **Verdict**: Keep (this is our only unique value-add for intelligent model routing)
**Lines 130-145: Manual Memory Search & Injection**
```python
memory_results = memory.search(query=query, user_id=user_id, limit=5)
relevant_memories = [entry.get('memory', '') for entry in memory_results.get("results", [])]
```
- **Verdict**: Remove (Mem0 handles this in chat context automatically)
**Lines 190-215: Manual Message Preparation with Memory Context**
```python
def _prepare_messages(self, query: str, context: Optional[List[ChatMessage]], memories: List[str]):
```
- **Verdict**: Remove (Mem0 integrates memory context automatically)
**Lines 242-276: Manual Memory Addition**
```python
async def add_memories(self, messages: List[ChatMessage], ...):
```
- **Verdict**: Simplify (use Mem0's native add() method directly)
**Lines 277-316: Custom Search Implementation**
```python
async def search_memories(self, query: str, ...):
```
- **Verdict**: Remove (use Mem0's native search() with filters)
**Lines 342-365: Custom Memory Update**
```python
async def update_memory(self, memory_id: str, ...):
```
- **Verdict**: Remove (use Mem0's native update() method)
**Lines 449-470: Custom Health Checks**
```python
async def health_check(self) -> Dict[str, str]:
```
- **Verdict**: Simplify (basic connectivity check only)
#### 🟢 **Logic We Should Keep (Unique Value)**
**Lines 22-35: Model Routing Setup**
- Our intelligent routing based on task complexity
- Custom OpenAI endpoint configuration
**Lines 94-103: Model Selection Logic**
- Time-sensitive task optimization
- Fallback model selection
**Lines 217-240: Response Generation with Fallback**
- Our custom endpoint integration
- Intelligent fallback logic
### File: `config.py`
#### 🟢 **Keep All Configuration**
- Custom OpenAI endpoint settings
- Model routing configuration
- This is our core differentiator
### File: `main.py` (API Layer)
#### 🔴 **Endpoints to Simplify**
- All memory CRUD endpoints can be simplified to direct Mem0 calls
- Remove custom response formatting inconsistencies
- Leverage Mem0's native response structures
## Refactoring Plan
### Phase 1: Documentation & Analysis ✅
- [x] Document Mem0 native capabilities
- [x] Identify duplicated logic
- [x] Create refactoring plan
### Phase 2: Core Refactoring
1. **Simplify Memory Operations**
- Remove manual memory search and injection logic
- Use Mem0's native chat context integration
- Remove custom memory preparation logic
2. **Leverage Native Categorization**
- Configure custom categories at project level
- Remove any custom categorization logic
3. **Use Native Filtering**
- Replace custom search with Mem0's advanced filtering
- Leverage built-in metadata and temporal filtering
4. **Simplify API Layer**
- Direct passthrough to Mem0 for most operations
- Standardize response format wrapper only
- Keep only model routing logic
### Phase 3: Enhanced Integration
1. **Enable Native Graph Memory**
- Configure `enable_graph=True` in project settings
- Remove any custom relationship logic
2. **Configure Custom Instructions**
- Set project-level memory handling instructions
- Remove hardcoded system prompts
3. **Optimize for Personal Assistant**
- Configure categories: personal_info, preferences, goals, work_context
- Set custom instructions for personal assistant behavior
## Expected Outcomes
### Code Reduction
- **~60% reduction** in `mem0_manager.py` (from 474 to ~200 lines)
- **Elimination** of custom memory logic
- **Focus** on intelligent model routing only
### Quality Improvements
- **Leverage proven memory intelligence** (+26% accuracy)
- **Faster responses** (91% improvement)
- **Lower token usage** (90% reduction)
- **Better conflict resolution** (native Mem0 logic)
- **Automatic relationship extraction** (native graph memory)
### Maintenance Benefits
- **Reduced custom code** to maintain
- **Leverage community expertise** (37.8k contributors)
- **Automatic improvements** from Mem0 updates
- **Focus on our core value-add** (intelligent routing)
## Implementation Priority
### High Priority (Essential)
1. Remove manual memory search and injection logic
2. Remove custom message preparation
3. Simplify memory CRUD to direct Mem0 calls
4. Configure native custom categories
### Medium Priority (Optimization)
1. Enable native graph memory
2. Configure custom instructions
3. Implement advanced filtering
4. Standardize API response format
### Low Priority (Polish)
1. Optimize health checks
2. Add monitoring for Mem0 native features
3. Update documentation
## Success Criteria
### Functional Parity
- [ ] All current endpoints work identically
- [ ] Memory operations maintain same behavior
- [ ] Model routing continues to work
- [ ] Performance matches or exceeds current implementation
### Code Quality
- [ ] Significant reduction in custom memory logic
- [ ] Cleaner, more maintainable codebase
- [ ] Better separation of concerns (routing vs memory)
- [ ] Improved error handling through Mem0's native error management
### Performance
- [ ] Faster memory operations (leveraging Mem0's optimizations)
- [ ] Lower token usage (Mem0's intelligent context injection)
- [ ] Better memory accuracy (Mem0's proven algorithms)
## Next Steps
1. **Get approval** for refactoring approach
2. **Start with Phase 2** - core refactoring
3. **Test each change** to ensure functional parity
4. **Document changes** as we go
5. **Measure performance** before/after
---
**Key Principle**: Trust the 37.8k star community's memory expertise, focus on our unique value-add (intelligent model routing).

220
README.md Normal file
View file

@ -0,0 +1,220 @@
# Mem0 Interface - Production Ready
A fully operational Mem0 interface with PostgreSQL and Neo4j integration, featuring intelligent model routing, comprehensive memory management, and production-grade monitoring.
## Features
### Core Memory System
- ✅ **Mem0 OSS Integration**: Complete hybrid datastore (Vector + Graph + KV storage)
- ✅ **PostgreSQL + pgvector**: High-performance vector embeddings storage
- ✅ **Neo4j 5.18**: Graph relationships with native vector similarity functions
- ✅ **Google Gemini Embeddings**: Enterprise-grade embedding generation
- ✅ **Memory Operations**: Store, search, update, delete memories with semantic search
- ✅ **Graph Relationships**: Automatic entity extraction and relationship mapping
### AI & Model Integration
- ✅ **Custom OpenAI Endpoint**: Integration with custom LLM endpoint
- ✅ **Memory-Enhanced Chat**: Context-aware conversations with long-term memory
- ✅ **Single Model Architecture**: Simplified, reliable claude-sonnet-4 integration
### Production Features
- ✅ **FastAPI Backend**: RESTful API with comprehensive error handling
- ✅ **Docker Compose**: Fully containerized deployment with health checks
- ✅ **Production Monitoring**: Real-time statistics and performance tracking
- ✅ **Structured Logging**: Correlation IDs and operation timing
- ✅ **Performance Analytics**: API usage patterns and response time monitoring
## Quick Start
1. **Prerequisites**:
- Docker and Docker Compose
- Custom OpenAI-compatible API endpoint access
- Google Gemini API key for embeddings
2. **Environment Setup**:
```bash
# Copy environment template
cp .env.example .env
# Update .env with your API keys:
OPENAI_COMPAT_API_KEY=sk-your-openai-compatible-key-here
EMBEDDER_API_KEY=AIzaSy-your-google-gemini-key-here
```
3. **Deploy Stack**:
```bash
# Start all services
docker-compose up --build -d
# Verify all services are healthy
curl http://localhost:8000/health
```
4. **Access Points**:
- **API**: http://localhost:8000
- **API Documentation**: http://localhost:8000/docs
- **Health Check**: http://localhost:8000/health
- **Global Statistics**: http://localhost:8000/stats
- **User Statistics**: http://localhost:8000/stats/{user_id}
## Architecture
### Core Components
- **FastAPI Backend**: Production-ready API with comprehensive monitoring
- **Mem0 OSS**: Hybrid memory management (vector + graph + key-value)
- **PostgreSQL + pgvector**: Vector embeddings storage and similarity search
- **Neo4j 5.18**: Graph relationships with native vector functions
- **Google Gemini**: Enterprise-grade embedding generation
### Monitoring & Observability
- **Request Tracing**: Correlation IDs for end-to-end tracking
- **Performance Timing**: Operation-level latency monitoring
- **Usage Analytics**: API call patterns and memory statistics
- **Error Tracking**: Structured error logging with context
- **Health Monitoring**: Real-time service status checks
## API Endpoints
### Chat with Memory
- `POST /chat` - Memory-enhanced conversations with context awareness
### Memory Management
- `POST /memories` - Add new memories from conversations
- `POST /memories/search` - Semantic search through stored memories
- `GET /memories/{user_id}` - Retrieve user-specific memories
- `PUT /memories` - Update existing memories
- `DELETE /memories/{memory_id}` - Remove specific memories
- `DELETE /memories/user/{user_id}` - Delete all user memories
### Graph Operations
- `GET /graph/relationships/{user_id}` - Graph relationships for user
### Monitoring & Analytics
- `GET /stats` - Global application statistics
- `GET /stats/{user_id}` - User-specific analytics and metrics
- `GET /health` - Service health and status check
- `GET /models` - Current model configuration
## Testing Examples
### 1. Health Check
```bash
curl http://localhost:8000/health
# Expected: All services show "healthy"
```
### 2. Add Memory
```bash
curl -X POST http://localhost:8000/memories \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"My name is Alice"}],"user_id":"alice"}'
# Expected: Memory extracted and stored with graph relationships
```
### 3. Search Memories
```bash
curl -X POST http://localhost:8000/memories/search \
-H "Content-Type: application/json" \
-d '{"query":"Alice","user_id":"alice"}'
# Expected: Returns stored memory with similarity score
```
### 4. Memory-Enhanced Chat
```bash
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"message":"What do you remember about me?","user_id":"alice"}'
# Expected: AI recalls stored information about Alice
```
### 5. Global Statistics
```bash
curl http://localhost:8000/stats
# Expected: Application usage statistics
{
"total_memories": 0,
"total_users": 1,
"api_calls_today": 5,
"avg_response_time_ms": 7106.26,
"memory_operations": {
"add": 1,
"search": 2,
"update": 0,
"delete": 0
},
"uptime_seconds": 137.1
}
```
### 6. User Analytics
```bash
curl http://localhost:8000/stats/alice
# Expected: User-specific metrics
{
"user_id": "alice",
"memory_count": 2,
"relationship_count": 2,
"last_activity": "2025-08-10T11:01:45.887157+00:00",
"api_calls_today": 1,
"avg_response_time_ms": 23091.93
}
```
### 7. Graph Relationships
```bash
curl http://localhost:8000/graph/relationships/alice
# Expected: Entity relationships extracted from memories
{
"relationships": [
{
"source": "Alice",
"relationship": "WORKS_AT",
"target": "Google"
}
],
"entities": ["Alice", "Google"],
"user_id": "alice"
}
```
## Troubleshooting
### Common Issues
1. **Neo4j Vector Function Error**
- **Problem**: `Unknown function 'vector.similarity.cosine'`
- **Solution**: Ensure Neo4j 5.18+ is used (not 5.15)
- **Fix**: Update docker-compose.yml to use `neo4j:5.18-community`
2. **Environment Variable Override**
- **Problem**: Shell environment variables override .env file
- **Solution**: Check `~/.zshrc` or `~/.bashrc` for conflicting exports
- **Fix**: Set values directly in docker-compose.yml
3. **Model Not Available**
- **Problem**: API returns "Invalid model name"
- **Solution**: Verify model availability on custom endpoint
- **Check**: `curl -H "Authorization: Bearer $API_KEY" $BASE_URL/v1/models`
4. **Ollama Connection Issues**
- **Problem**: Embedding generation fails
- **Solution**: Ensure Ollama is running with nomic-embed-text model
- **Check**: `ollama list` should show `nomic-embed-text:latest`
### Service Dependencies
- **Neo4j**: Must start before backend for vector functions
- **PostgreSQL**: Required for vector storage initialization
- **Ollama**: Must be running locally on port 11434
- **API Endpoint**: Must have valid models available
## Production Notes
- **Memory Usage**: Neo4j and PostgreSQL require adequate RAM for vector operations
- **API Rate Limits**: Monitor usage on custom endpoint
- **Data Persistence**: All data stored in Docker volumes
- **Scaling**: Individual services can be scaled independently
- **Security**: API keys are passed through environment variables
## Development
See individual README files in `backend/` and `frontend/` directories for development setup.

513
TESTING.md Normal file
View file

@ -0,0 +1,513 @@
# Mem0 Interface POC - Testing Guide
This guide provides comprehensive testing instructions and cURL examples for all API endpoints.
## 🚀 Quick Setup and Testing
### 1. Start the Services
```bash
# Copy and configure environment
cp .env.example .env
# Edit .env with your API keys and settings
# Start all services
docker-compose up -d
# Check services are running
docker-compose ps
```
### 2. Wait for Services to Initialize
```bash
# Check backend health (wait until all services are healthy)
curl -s http://localhost:8000/health | jq
# Check databases are ready
docker-compose logs postgres | grep "ready to accept connections"
docker-compose logs neo4j | grep "Started"
```
### 3. Basic Health Check
```bash
curl -X GET "http://localhost:8000/health" \
-H "Content-Type: application/json" | jq
```
Expected response:
```json
{
"status": "healthy",
"services": {
"openai_endpoint": "healthy",
"memory_o4-mini": "healthy",
"memory_claude-sonnet-4": "healthy",
"memory_gemini-2.5-pro": "healthy",
"memory_o3": "healthy"
},
"timestamp": "2024-12-28T10:30:00.000Z"
}
```
## 📋 Complete API Testing
### 1. Model Information
```bash
# Get available models and routing configuration
curl -X GET "http://localhost:8000/models" | jq
```
Expected response:
```json
{
"available_models": {
"fast": "o4-mini",
"analytical": "gemini-2.5-pro",
"reasoning": "claude-sonnet-4",
"expert": "o3",
"extraction": "o4-mini"
},
"model_routing": {
"simple": "o4-mini",
"moderate": "gemini-2.5-pro",
"complex": "claude-sonnet-4",
"expert": "o3"
}
}
```
### 2. Enhanced Chat with Memory
#### Simple Chat (should route to o4-mini)
```bash
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "Hello, my name is Alice and I live in San Francisco",
"user_id": "alice_test",
"enable_graph": true
}' | jq
```
#### Complex Chat (should route to claude-sonnet-4)
```bash
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "Can you help me design a comprehensive architecture for a distributed microservices system that needs to handle high throughput?",
"user_id": "alice_test",
"enable_graph": true
}' | jq
```
#### Expert Chat (should route to o3)
```bash
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "I need to research and optimize a complex machine learning pipeline for real-time fraud detection with comprehensive evaluation metrics",
"user_id": "alice_test",
"enable_graph": true
}' | jq
```
#### Chat with Context
```bash
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "What did I tell you about my location?",
"user_id": "alice_test",
"context": [
{"role": "user", "content": "I need help with travel planning"},
{"role": "assistant", "content": "I'd be happy to help with travel planning!"}
],
"enable_graph": true
}' | jq
```
#### Force Specific Model
```bash
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "Simple question: what is 2+2?",
"user_id": "alice_test",
"force_model": "o3",
"enable_graph": true
}' | jq
```
Expected chat response structure:
```json
{
"response": "Hello Alice! Nice to meet you. I've noted that you're located in San Francisco...",
"model_used": "o4-mini",
"complexity": "simple",
"memories_used": 0,
"estimated_tokens": 45,
"task_metrics": {
"complexity": "simple",
"estimated_tokens": 45,
"requires_memory": false,
"is_time_sensitive": false,
"context_length": 0
}
}
```
### 3. Memory Management
#### Add Memories Manually
```bash
curl -X POST "http://localhost:8000/memories" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "I work as a software engineer at Google"},
{"role": "assistant", "content": "That's great! What kind of projects do you work on?"},
{"role": "user", "content": "I focus on machine learning infrastructure"}
],
"user_id": "alice_test",
"metadata": {"topic": "career", "importance": "high"},
"enable_graph": true
}' | jq
```
#### Search Memories
```bash
curl -X POST "http://localhost:8000/memories/search" \
-H "Content-Type: application/json" \
-d '{
"query": "Where does Alice work?",
"user_id": "alice_test",
"limit": 5
}' | jq
```
#### Get All User Memories
```bash
curl -X GET "http://localhost:8000/memories/alice_test?limit=10" | jq
```
#### Update Memory (you'll need a real memory_id from previous responses)
```bash
# First get memories to find an ID
MEMORY_ID=$(curl -s -X GET "http://localhost:8000/memories/alice_test?limit=1" | jq -r '.[0].id')
curl -X PUT "http://localhost:8000/memories" \
-H "Content-Type: application/json" \
-d '{
"memory_id": "'$MEMORY_ID'",
"content": "Alice works as a senior software engineer at Google, specializing in ML infrastructure",
"metadata": {"topic": "career", "importance": "high", "updated": true}
}' | jq
```
#### Delete Specific Memory
```bash
curl -X DELETE "http://localhost:8000/memories/$MEMORY_ID" | jq
```
#### Delete All User Memories
```bash
curl -X DELETE "http://localhost:8000/memories/user/alice_test" | jq
```
### 4. Graph Relationships
```bash
# Get graph relationships for a user
curl -X GET "http://localhost:8000/graph/relationships/alice_test" | jq
```
Expected graph response:
```json
{
"relationships": [
{
"source": "Alice",
"relationship": "WORKS_AT",
"target": "Google",
"properties": {}
},
{
"source": "Alice",
"relationship": "LIVES_IN",
"target": "San Francisco",
"properties": {}
}
],
"entities": ["Alice", "Google", "San Francisco"],
"user_id": "alice_test"
}
```
## 🧪 Test Scenarios
### Scenario 1: User Onboarding and Profile Building
```bash
# Step 1: Initial introduction
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "Hi, I'\''m Bob. I'\''m a data scientist at Microsoft in Seattle. I love hiking and photography.",
"user_id": "bob_test"
}' | jq
# Step 2: Add work preferences
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "I prefer working with Python and PyTorch for my machine learning projects.",
"user_id": "bob_test"
}' | jq
# Step 3: Test memory recall
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "What programming languages do I prefer?",
"user_id": "bob_test"
}' | jq
# Step 4: Check stored memories
curl -X GET "http://localhost:8000/memories/bob_test" | jq
# Step 5: View relationships
curl -X GET "http://localhost:8000/graph/relationships/bob_test" | jq
```
### Scenario 2: Multi-User Isolation Testing
```bash
# Create memories for User 1
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "My favorite food is pizza",
"user_id": "user1"
}' | jq
# Create memories for User 2
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "My favorite food is sushi",
"user_id": "user2"
}' | jq
# Test isolation - User 1 should only see their own memories
curl -X POST "http://localhost:8000/memories/search" \
-H "Content-Type: application/json" \
-d '{
"query": "favorite food",
"user_id": "user1"
}' | jq
# Test isolation - User 2 should only see their own memories
curl -X POST "http://localhost:8000/memories/search" \
-H "Content-Type: application/json" \
-d '{
"query": "favorite food",
"user_id": "user2"
}' | jq
```
### Scenario 3: Memory Evolution and Conflict Resolution
```bash
# Initial preference
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "I really dislike coffee, I prefer tea",
"user_id": "charlie_test"
}' | jq
# Changed preference (should update memory)
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "Actually, I'\''ve started to really enjoy coffee now, especially espresso",
"user_id": "charlie_test"
}' | jq
# Test current preference
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "What do I think about coffee?",
"user_id": "charlie_test"
}' | jq
# Check memory evolution
curl -X GET "http://localhost:8000/memories/charlie_test" | jq
```
### Scenario 4: Model Routing Validation
```bash
# Simple task (should use o4-mini)
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "What is the capital of France?",
"user_id": "routing_test"
}' | jq '.model_used'
# Analytical task (should use gemini-2.5-pro)
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "Can you analyze the pros and cons of microservices vs monolithic architecture?",
"user_id": "routing_test"
}' | jq '.model_used'
# Complex reasoning (should use claude-sonnet-4)
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "Help me design a strategy for implementing a new software development process across multiple teams",
"user_id": "routing_test"
}' | jq '.model_used'
# Expert task (should use o3)
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "I need to research and optimize a comprehensive distributed system architecture with multiple databases, caching layers, and real-time processing requirements",
"user_id": "routing_test"
}' | jq '.model_used'
```
## 🔍 Monitoring and Debugging
### Check Service Logs
```bash
# Backend logs
docker-compose logs -f backend
# Database logs
docker-compose logs -f postgres
docker-compose logs -f neo4j
# All logs
docker-compose logs -f
```
### Database Direct Access
```bash
# PostgreSQL
docker-compose exec postgres psql -U mem0_user -d mem0_db
# Check tables
\dt
# Check embeddings
SELECT id, user_id, content, created_at FROM embeddings LIMIT 5;
# Neo4j Browser
# Open http://localhost:7474 in browser
# Username: neo4j, Password: mem0_neo4j_password
# Check nodes and relationships
MATCH (n) RETURN n LIMIT 10;
MATCH ()-[r]->() RETURN r LIMIT 10;
```
### Performance Testing
```bash
# Simple load test with curl
for i in {1..10}; do
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "Test message '$i'",
"user_id": "load_test_user"
}' &
done
wait
# Check response times
time curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"message": "What is machine learning?",
"user_id": "perf_test"
}'
```
## ✅ Expected Results Checklist
After running the tests, verify:
- [ ] Health check shows all services healthy
- [ ] Chat responses are generated using appropriate models
- [ ] Memories are stored and retrievable
- [ ] Memory search returns relevant results
- [ ] Graph relationships are created and accessible
- [ ] User isolation works correctly
- [ ] Memory updates and deletions work
- [ ] Model routing works as expected
- [ ] No errors in service logs
- [ ] Database connections are stable
## 🐛 Troubleshooting
### Common Issues
1. **"No memory instance available"**
- Check if databases are running: `docker-compose ps`
- Verify environment variables in `.env`
- Check backend logs: `docker-compose logs backend`
2. **OpenAI endpoint errors**
- Verify `OPENAI_API_KEY` and `OPENAI_BASE_URL` in `.env`
- Test endpoint directly: `curl -H "Authorization: Bearer $OPENAI_API_KEY" $OPENAI_BASE_URL/models`
3. **Memory search returns empty results**
- Ensure memories were added first
- Check user_id matches between add and search
- Verify pgvector extension: `docker-compose exec postgres psql -U mem0_user -d mem0_db -c "\dx"`
4. **Graph relationships not appearing**
- Check if `enable_graph: true` is set
- Verify Neo4j is running with APOC: `docker-compose logs neo4j | grep -i apoc`
- Check Neo4j connectivity: open http://localhost:7474
### Reset Everything
```bash
# Stop all services
docker-compose down -v
# Remove all data
docker volume prune -f
# Restart fresh
docker-compose up -d
```
## 📊 Performance Expectations
With optimal configuration:
- Health check: < 100ms
- Simple chat: < 2s (depends on o4-mini speed)
- Complex chat: < 10s (depends on model)
- Memory search: < 500ms
- Memory add: < 1s
- Graph queries: < 1s
Performance will vary based on:
- Custom endpoint response times
- Database hardware/configuration
- Network latency
- Query complexity

30
backend/Dockerfile Normal file
View file

@ -0,0 +1,30 @@
FROM python:3.11-slim
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Set Python path
ENV PYTHONPATH=/app
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Default command (can be overridden by docker-compose)
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

450
backend/README.md Normal file
View file

@ -0,0 +1,450 @@
# Mem0 Interface Backend
A production-ready FastAPI backend that provides intelligent memory integration with Mem0, featuring comprehensive memory management, real-time monitoring, and enterprise-grade observability.
## 🏗️ Architecture Overview
### Core Components
1. **Mem0Manager** (`mem0_manager.py`)
- Central orchestration of memory operations
- Integration with custom OpenAI-compatible endpoint
- Memory persistence across PostgreSQL and Neo4j
- Performance timing and operation tracking
2. **Configuration System** (`config.py`)
- Environment-based configuration management
- Database connection management
- Security and CORS settings
- API endpoint configuration
3. **API Layer** (`main.py`)
- RESTful endpoints for all memory operations
- Request middleware with correlation IDs
- Real-time statistics and monitoring endpoints
- Enhanced error handling and logging
4. **Data Models** (`models.py`)
- Pydantic models for request/response validation
- Statistics and monitoring response models
- Type safety and automatic documentation
5. **Monitoring System** (`monitoring.py`)
- Thread-safe statistics collection
- Performance timing decorators
- Correlation ID generation
- Real-time analytics tracking
### Database Architecture
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ PostgreSQL │ │ Neo4j │ │ Custom LLM │
│ (pgvector) │ │ (APOC) │ │ Endpoint │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ • Vector Store │ │ • Graph Store │ │ • claude-sonnet-4│
│ • Embeddings │ │ • Relationships │ │ • Gemini Embed │
│ • Memory History│ │ • Entity Links │ │ • Single Model │
│ • Metadata │ │ • APOC Functions│ │ • Reliable API │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└───────────────────────┼───────────────────────┘
┌─────────────────┐
│ Mem0 Manager │
│ │
│ • Memory Ops │
│ • Performance │
│ • Monitoring │
│ • Analytics │
└─────────────────┘
┌─────────────────┐
│ Monitoring │
│ │
│ • Correlation │
│ • Timing │
│ • Statistics │
│ • Health │
└─────────────────┘
```
### Production Monitoring
```python
@timed("operation_name") # Automatic timing and logging
async def memory_operation():
# Operation with full observability
pass
# Request tracing with correlation IDs
correlation_id = generate_correlation_id() # 8-char unique ID
# Real-time statistics collection
stats.record_api_call(user_id, response_time_ms)
stats.record_memory_operation("add")
```
**Monitoring Features:**
- Request correlation IDs for end-to-end tracing
- Performance timing for all operations
- Real-time API usage statistics
- Memory operation breakdown
- Error tracking with context
## 🚀 Features
### Core Memory Operations
- ✅ **Intelligent Chat**: Context-aware conversations with memory
- ✅ **Memory CRUD**: Add, search, update, delete memories
- ✅ **Semantic Search**: Vector-based similarity search
- ✅ **Graph Relationships**: Entity extraction and relationship mapping
- ✅ **User Isolation**: Separate memory spaces per user_id
- ✅ **Memory History**: Track memory evolution over time
### Production Features
- ✅ **Custom Endpoint Integration**: Full support for OpenAI-compatible endpoints
- ✅ **Real-time Monitoring**: Performance timing and usage analytics
- ✅ **Request Tracing**: Correlation IDs for end-to-end debugging
- ✅ **Statistics APIs**: Global and user-specific metrics endpoints
- ✅ **Graph Memory**: Neo4j-powered relationship tracking
- ✅ **Health Monitoring**: Comprehensive service health checks
- ✅ **Structured Logging**: Enhanced error tracking and debugging
## 📁 File Structure
```
backend/
├── main.py # FastAPI application and endpoints
├── mem0_manager.py # Core Mem0 integration with timing
├── monitoring.py # Observability and statistics system
├── config.py # Configuration management
├── models.py # Pydantic data models (including stats)
├── requirements.txt # Python dependencies
├── Dockerfile # Container configuration
└── README.md # This file
```
## 🔧 Configuration
### Environment Variables
| Variable | Description | Default | Required |
|----------|-------------|---------|----------|
| `OPENAI_COMPAT_API_KEY` | Your custom endpoint API key | - | ✅ |
| `OPENAI_BASE_URL` | Custom endpoint URL | - | ✅ |
| `EMBEDDER_API_KEY` | Google Gemini API key for embeddings | - | ✅ |
| `POSTGRES_HOST` | PostgreSQL host | postgres | ✅ |
| `POSTGRES_PORT` | PostgreSQL port | 5432 | ✅ |
| `POSTGRES_DB` | Database name | mem0_db | ✅ |
| `POSTGRES_USER` | Database user | mem0_user | ✅ |
| `POSTGRES_PASSWORD` | Database password | - | ✅ |
| `NEO4J_URI` | Neo4j connection URI | bolt://neo4j:7687 | ✅ |
| `NEO4J_USERNAME` | Neo4j username | neo4j | ✅ |
| `NEO4J_PASSWORD` | Neo4j password | - | ✅ |
| `DEFAULT_MODEL` | Default model for general use | claude-sonnet-4 | ❌ |
| `LOG_LEVEL` | Logging level | INFO | ❌ |
| `CORS_ORIGINS` | Allowed CORS origins | http://localhost:3000 | ❌ |
### Production Configuration
Current production setup uses a simplified, reliable architecture:
```python
# Single model approach for stability
DEFAULT_MODEL = "claude-sonnet-4"
# Embeddings via Google Gemini
EMBEDDER_CONFIG = {
"provider": "gemini",
"model": "models/gemini-embedding-001",
"embedding_dims": 1536
}
# Monitoring configuration
MONITORING_CONFIG = {
"correlation_ids": True,
"operation_timing": True,
"statistics_collection": True,
"slow_request_threshold": 2000 # ms
}
```
## 🔗 API Endpoints
### Core Chat
- `POST /chat` - Enhanced chat with memory integration
### Memory Management
- `POST /memories` - Add memories manually
- `POST /memories/search` - Search memories semantically
- `GET /memories/{user_id}` - Get user memories
- `PUT /memories` - Update specific memory
- `DELETE /memories/{memory_id}` - Delete memory
- `DELETE /memories/user/{user_id}` - Delete all user memories
### Graph Operations
- `GET /graph/relationships/{user_id}` - Get user relationship graph
### Monitoring & Analytics
- `GET /stats` - Global application statistics
- `GET /stats/{user_id}` - User-specific metrics
- `GET /health` - Service health check
- `GET /models` - Available models and configuration
## 🏃‍♂️ Quick Start
1. **Set up environment:**
```bash
cp ../.env.example ../.env
# Edit .env with your configuration
```
2. **Install dependencies:**
```bash
pip install -r requirements.txt
```
3. **Run the server:**
```bash
uvicorn main:app --reload --host 0.0.0.0 --port 8000
```
4. **Check health:**
```bash
curl http://localhost:8000/health
```
5. **View API docs:**
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
## 🧪 Testing
### Verified Test Results (2025-01-05)
All core functionality has been tested and verified as working:
#### 1. Health Check ✅
```bash
curl http://localhost:8000/health
# Result: All memory services show "healthy" status
# - openai_endpoint: healthy
# - memory_o4-mini: healthy
# - memory_gemini-2.5-pro: healthy
# - memory_claude-sonnet-4: healthy
# - memory_o3: healthy
```
#### 2. Memory Operations ✅
```bash
# Add Memory
curl -X POST http://localhost:8000/memories \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"My name is Alice"}],"user_id":"alice"}'
# Result: Memory successfully extracted and stored with graph relationships
# Search Memory
curl -X POST http://localhost:8000/memories/search \
-H "Content-Type: application/json" \
-d '{"query":"Alice","user_id":"alice"}'
# Result: Returns stored memory with similarity score 0.097
```
#### 3. Memory-Enhanced Chat ✅
```bash
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"message":"What do you remember about me?","user_id":"alice"}'
# Result: "I remember that your name is Alice"
# memories_used: 1 (successfully retrieved and used stored memory)
```
#### 4. Intelligent Model Routing ✅
```bash
# Simple task → o4-mini
curl -X POST http://localhost:8000/chat \
-d '{"message":"Hi there","user_id":"test"}'
# Result: model_used: "o4-mini", complexity: "simple"
# Expert task → o3
curl -X POST http://localhost:8000/chat \
-d '{"message":"Please analyze the pros and cons of microservices","user_id":"test"}'
# Result: model_used: "o3", complexity: "expert"
```
#### 5. Neo4j Vector Functions ✅
```bash
# Verified Neo4j 5.18 vector.similarity.cosine() function
docker exec mem0-neo4j cypher-shell -u neo4j -p mem0_neo4j_password \
"RETURN vector.similarity.cosine([1.0, 2.0, 3.0], [1.0, 2.0, 3.0]) AS similarity;"
# Result: similarity = 1.0 (perfect match)
```
### Key Integration Points Verified
- ✅ **Ollama Embeddings**: nomic-embed-text:latest working for vector generation
- ✅ **PostgreSQL + pgvector**: Vector storage and similarity search operational
- ✅ **Neo4j 5.18**: Graph relationships and native vector functions working
- ✅ **Custom LLM Endpoint**: All 4 models accessible and routing correctly
- ✅ **Memory Persistence**: Data survives container restarts via Docker volumes
## 🔍 Production Monitoring
### Structured Logging with Correlation IDs
- JSON-formatted logs with correlation IDs for request tracing
- Performance timing for all operations
- Enhanced error tracking with operation context
- Slow request detection (>2 seconds)
### Real-time Statistics Endpoints
#### Global Statistics (`GET /stats`)
```json
{
"total_memories": 0,
"total_users": 1,
"api_calls_today": 5,
"avg_response_time_ms": 7106.26,
"memory_operations": {
"add": 1,
"search": 2,
"update": 0,
"delete": 0
},
"uptime_seconds": 137.1
}
```
#### User Analytics (`GET /stats/{user_id}`)
```json
{
"user_id": "alice",
"memory_count": 2,
"relationship_count": 2,
"last_activity": "2025-08-10T11:01:45.887157+00:00",
"api_calls_today": 1,
"avg_response_time_ms": 23091.93
}
```
#### Health Monitoring (`GET /health`)
```json
{
"status": "healthy",
"services": {
"openai_endpoint": "healthy",
"mem0_memory": "healthy"
},
"timestamp": "2025-08-10T11:01:05.734615"
}
```
### Performance Tracking Features
- **Correlation IDs**: 8-character unique identifiers for request tracing
- **Operation Timing**: Automatic timing for all memory operations
- **Statistics Collection**: Thread-safe in-memory analytics
- **Error Context**: Enhanced error messages with operation details
- **Slow Request Alerts**: Automatic logging of requests >2 seconds
## 🐛 Troubleshooting
### Resolved Issues & Solutions
#### 1. **Neo4j Vector Function Error** ✅ RESOLVED
- **Problem**: `Unknown function 'vector.similarity.cosine'`
- **Root Cause**: Neo4j 5.15 doesn't support vector functions (introduced in 5.18)
- **Solution**: Upgraded to Neo4j 5.18-community
- **Fix Applied**: Updated docker-compose.yml: `image: neo4j:5.18-community`
#### 2. **Environment Variable Override** ✅ RESOLVED
- **Problem**: Shell environment variables overriding .env file
- **Root Cause**: `~/.zshrc` exports took precedence over Docker Compose .env
- **Solution**: Set values directly in docker-compose.yml environment section
- **Fix Applied**: Hard-coded API keys in docker-compose.yml
#### 3. **Model Availability Issues** ✅ RESOLVED
- **Problem**: `gemini-2.5-pro` showing as unavailable
- **Root Cause**: Incorrect API endpoint configuration
- **Solution**: Verified models with `/v1/models` endpoint, updated API keys
- **Fix Applied**: Now all models (o4-mini, gemini-2.5-pro, claude-sonnet-4, o3) operational
#### 4. **Memory Initialization Failures** ✅ RESOLVED
- **Problem**: "No memory instance available" errors
- **Root Cause**: Neo4j container starting after backend, vector functions missing
- **Solution**: Sequential startup + Neo4j 5.18 upgrade
- **Fix Applied**: All memory instances now healthy
### Current Known Working Configuration
#### Docker Compose Settings
```yaml
neo4j:
image: neo4j:5.18-community # Critical: Must be 5.18+ for vector functions
backend:
environment:
OPENAI_API_KEY: sk-your-api-key-here # Set in docker-compose.yml
OPENAI_BASE_URL: https://your-openai-compatible-endpoint.com/v1
```
#### Dependency Requirements
- Neo4j 5.18+ (for vector.similarity.cosine function)
- Ollama running locally with nomic-embed-text:latest
- PostgreSQL with pgvector extension
- Valid API keys for custom LLM endpoint
### Debugging Commands
```bash
# Check Neo4j vector function availability
docker exec mem0-neo4j cypher-shell -u neo4j -p mem0_neo4j_password \
"RETURN vector.similarity.cosine([1.0, 0.0], [1.0, 0.0]) AS test;"
# Verify API endpoint models
curl -H "Authorization: Bearer $API_KEY" $BASE_URL/v1/models | jq '.data[].id'
# Check Ollama embeddings
curl http://host.docker.internal:11434/api/tags
# Monitor backend logs
docker logs mem0-backend --tail 20 -f
```
## 🔒 Security Considerations
- API keys are loaded from environment variables
- CORS is configured for specified origins
- Input validation via Pydantic models
- Structured logging excludes sensitive data
- Database connections use authentication
## 📈 Performance
### Optimizations Implemented
- Model-specific parameter tuning
- Intelligent routing to reduce costs
- Memory caching within Mem0
- Efficient database queries
- Structured logging for monitoring
### Expected Performance
- Sub-50ms memory retrieval (with optimized setup)
- 90% token reduction through smart context injection
- Intelligent model routing for cost efficiency
## 🔄 Development
### Adding New Models
1. Add model configuration in `config.py`
2. Update routing logic in `mem0_manager.py`
3. Add model-specific parameters
4. Test with health checks
### Adding New Endpoints
1. Define Pydantic models in `models.py`
2. Implement logic in `mem0_manager.py`
3. Add FastAPI endpoint in `main.py`
4. Update documentation and tests
## 📄 License
This POC is designed for demonstration and evaluation purposes.

52
backend/config.py Normal file
View file

@ -0,0 +1,52 @@
"""Configuration management for Mem0 Interface POC."""
import os
from typing import List, Optional
from pydantic import Field
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
# API Configuration
openai_api_key: str = Field(..., env="OPENAI_API_KEY")
openai_base_url: str = Field(..., env="OPENAI_BASE_URL")
embedder_api_key: str = Field(..., env="EMBEDDER_API_KEY")
# Database Configuration
postgres_host: str = Field("localhost", env="POSTGRES_HOST")
postgres_port: int = Field(5432, env="POSTGRES_PORT")
postgres_db: str = Field("mem0_db", env="POSTGRES_DB")
postgres_user: str = Field("mem0_user", env="POSTGRES_USER")
postgres_password: str = Field("mem0_password", env="POSTGRES_PASSWORD")
# Neo4j Configuration
neo4j_uri: str = Field("bolt://localhost:7687", env="NEO4J_URI")
neo4j_username: str = Field("neo4j", env="NEO4J_USERNAME")
neo4j_password: str = Field("mem0_neo4j_password", env="NEO4J_PASSWORD")
# Application Configuration
log_level: str = Field("INFO", env="LOG_LEVEL")
cors_origins: str = Field("http://localhost:3000", env="CORS_ORIGINS")
# Model Configuration - Ultra-minimal (single model)
default_model: str = Field("claude-sonnet-4", env="DEFAULT_MODEL")
@property
def postgres_url(self) -> str:
"""Build PostgreSQL connection URL."""
return f"postgresql://{self.postgres_user}:{self.postgres_password}@{self.postgres_host}:{self.postgres_port}/{self.postgres_db}"
@property
def cors_origins_list(self) -> List[str]:
"""Convert CORS origins string to list."""
return [origin.strip() for origin in self.cors_origins.split(",")]
class Config:
env_file = ".env"
case_sensitive = False
# Global settings instance
settings = Settings()

476
backend/main.py Normal file
View file

@ -0,0 +1,476 @@
"""Main FastAPI application for Mem0 Interface POC."""
import logging
import time
from datetime import datetime
from typing import List, Dict, Any, Optional
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import structlog
from config import settings
from models import (
ChatRequest, MemoryAddRequest, MemoryAddResponse,
MemorySearchRequest, MemorySearchResponse, MemoryUpdateRequest,
MemoryItem, GraphResponse, HealthResponse, ErrorResponse,
GlobalStatsResponse, UserStatsResponse
)
from mem0_manager import mem0_manager
# Configure structured logging
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
structlog.processors.JSONRenderer()
],
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)
logger = structlog.get_logger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan manager."""
# Startup
logger.info("Starting Mem0 Interface POC")
# Perform health check on startup
health_status = await mem0_manager.health_check()
unhealthy_services = [k for k, v in health_status.items() if "unhealthy" in v]
if unhealthy_services:
logger.warning(f"Some services are unhealthy: {unhealthy_services}")
else:
logger.info("All services are healthy")
yield
# Shutdown
logger.info("Shutting down Mem0 Interface POC")
# Initialize FastAPI app
app = FastAPI(
title="Mem0 Interface POC",
description="Minimal but fully functional Mem0 interface with PostgreSQL and Neo4j integration",
version="1.0.0",
lifespan=lifespan
)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins_list,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Request logging middleware with monitoring
@app.middleware("http")
async def log_requests(request, call_next):
"""Log all HTTP requests with correlation ID and timing."""
from monitoring import generate_correlation_id, stats
correlation_id = generate_correlation_id()
start_time = time.time()
# Extract user_id from request if available
user_id = None
if request.method == "POST":
# Try to extract user_id from request body for POST requests
try:
body = await request.body()
if body:
import json
data = json.loads(body)
user_id = data.get('user_id')
except:
pass
elif "user_id" in str(request.url.path):
# Extract user_id from path for GET requests
path_parts = request.url.path.split('/')
if len(path_parts) > 2 and path_parts[-2] in ['memories', 'stats']:
user_id = path_parts[-1]
# Log start of request
logger.info(
"HTTP request started",
correlation_id=correlation_id,
method=request.method,
path=request.url.path,
user_id=user_id
)
response = await call_next(request)
process_time = time.time() - start_time
process_time_ms = process_time * 1000
# Record statistics
stats.record_api_call(user_id, process_time_ms)
# Log completion with enhanced details
if process_time_ms > 2000: # Slow request threshold
logger.warning(
"HTTP request completed (SLOW)",
correlation_id=correlation_id,
method=request.method,
path=request.url.path,
status_code=response.status_code,
process_time_ms=round(process_time_ms, 2),
user_id=user_id,
slow_request=True
)
elif response.status_code >= 400:
logger.error(
"HTTP request completed (ERROR)",
correlation_id=correlation_id,
method=request.method,
path=request.url.path,
status_code=response.status_code,
process_time_ms=round(process_time_ms, 2),
user_id=user_id,
slow_request=False
)
else:
logger.info(
"HTTP request completed",
correlation_id=correlation_id,
method=request.method,
path=request.url.path,
status_code=response.status_code,
process_time_ms=round(process_time_ms, 2),
user_id=user_id,
slow_request=False
)
return response
# Exception handlers
@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
"""Global exception handler."""
logger.error(f"Unhandled exception: {exc}", exc_info=True)
return JSONResponse(
status_code=500,
content={"error": "Internal server error", "detail": str(exc)}
)
# Health check endpoint
@app.get("/health", response_model=HealthResponse)
async def health_check():
"""Check the health of all services."""
try:
services = await mem0_manager.health_check()
overall_status = "healthy" if all("healthy" in status for status in services.values()) else "degraded"
return HealthResponse(
status=overall_status,
services=services,
timestamp=datetime.utcnow().isoformat()
)
except Exception as e:
logger.error(f"Health check failed: {e}")
return HealthResponse(
status="unhealthy",
services={"error": str(e)},
timestamp=datetime.utcnow().isoformat()
)
# Core chat endpoint with memory enhancement
@app.post("/chat")
async def chat_with_memory(request: ChatRequest):
"""Ultra-minimal chat endpoint - pure Mem0 + custom endpoint."""
try:
logger.info(f"Processing chat request for user: {request.user_id}")
result = await mem0_manager.chat_with_memory(
message=request.message,
user_id=request.user_id,
context=request.context,
metadata=request.metadata
)
return result
except Exception as e:
logger.error(f"Error in chat endpoint: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Memory management endpoints - pure Mem0 passthroughs
@app.post("/memories")
async def add_memories(request: MemoryAddRequest):
"""Add memories - pure Mem0 passthrough."""
try:
logger.info(f"Adding memories for user: {request.user_id}")
result = await mem0_manager.add_memories(
messages=request.messages,
user_id=request.user_id,
agent_id=request.agent_id,
run_id=request.run_id,
metadata=request.metadata
)
return result
except Exception as e:
logger.error(f"Error adding memories: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/memories/search")
async def search_memories(request: MemorySearchRequest):
"""Search memories - pure Mem0 passthrough."""
try:
logger.info(f"Searching memories for user: {request.user_id}, query: {request.query}")
result = await mem0_manager.search_memories(
query=request.query,
user_id=request.user_id,
limit=request.limit,
threshold=request.threshold,
filters=request.filters,
agent_id=request.agent_id,
run_id=request.run_id
)
return result
except Exception as e:
logger.error(f"Error searching memories: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/memories/{user_id}")
async def get_user_memories(
user_id: str,
limit: int = 10,
agent_id: Optional[str] = None,
run_id: Optional[str] = None
):
"""Get all memories for a user with hierarchy filtering - pure Mem0 passthrough."""
try:
logger.info(f"Retrieving memories for user: {user_id}")
memories = await mem0_manager.get_user_memories(
user_id=user_id,
limit=limit,
agent_id=agent_id,
run_id=run_id
)
return memories
except Exception as e:
logger.error(f"Error retrieving user memories: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.put("/memories")
async def update_memory(request: MemoryUpdateRequest):
"""Update memory - pure Mem0 passthrough."""
try:
logger.info(f"Updating memory: {request.memory_id}")
result = await mem0_manager.update_memory(
memory_id=request.memory_id,
content=request.content,
)
return result
except Exception as e:
logger.error(f"Error updating memory: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.delete("/memories/{memory_id}")
async def delete_memory(memory_id: str):
"""Delete a specific memory."""
try:
logger.info(f"Deleting memory: {memory_id}")
result = await mem0_manager.delete_memory(memory_id=memory_id)
return result
except Exception as e:
logger.error(f"Error deleting memory: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.delete("/memories/user/{user_id}")
async def delete_user_memories(user_id: str):
"""Delete all memories for a specific user."""
try:
logger.info(f"Deleting all memories for user: {user_id}")
result = await mem0_manager.delete_user_memories(user_id=user_id)
return result
except Exception as e:
logger.error(f"Error deleting user memories: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Graph relationships endpoint - pure Mem0 passthrough
@app.get("/graph/relationships/{user_id}")
async def get_graph_relationships(user_id: str):
"""Get graph relationships - pure Mem0 passthrough."""
try:
logger.info(f"Retrieving graph relationships for user: {user_id}")
result = await mem0_manager.get_graph_relationships(user_id=user_id)
return result
except Exception as e:
logger.error(f"Error retrieving graph relationships: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Memory history endpoint - new feature
@app.get("/memories/{memory_id}/history")
async def get_memory_history(memory_id: str):
"""Get memory change history - pure Mem0 passthrough."""
try:
logger.info(f"Retrieving history for memory: {memory_id}")
result = await mem0_manager.get_memory_history(memory_id=memory_id)
return result
except Exception as e:
logger.error(f"Error retrieving memory history: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Statistics and monitoring endpoints
@app.get("/stats", response_model=GlobalStatsResponse)
async def get_global_stats():
"""Get global application statistics."""
try:
from monitoring import stats
# Get basic stats from monitoring
basic_stats = stats.get_global_stats()
# Get actual memory count from Mem0 (simplified approach)
try:
# This is a rough estimate - in production you might want a more efficient method
sample_result = await mem0_manager.search_memories(query="*", user_id="__stats_check__", limit=1)
# For now, we'll use the basic stats total_memories value
# You could implement a more accurate count by querying the database directly
total_memories = basic_stats['total_memories'] # Will be 0 for now
except:
total_memories = 0
return GlobalStatsResponse(
total_memories=total_memories,
total_users=basic_stats['total_users'],
api_calls_today=basic_stats['api_calls_today'],
avg_response_time_ms=basic_stats['avg_response_time_ms'],
memory_operations={
"add": basic_stats['memory_operations']['add'],
"search": basic_stats['memory_operations']['search'],
"update": basic_stats['memory_operations']['update'],
"delete": basic_stats['memory_operations']['delete']
},
uptime_seconds=basic_stats['uptime_seconds']
)
except Exception as e:
logger.error(f"Error getting global stats: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/stats/{user_id}", response_model=UserStatsResponse)
async def get_user_stats(user_id: str):
"""Get user-specific statistics."""
try:
from monitoring import stats
# Get basic user stats from monitoring
basic_stats = stats.get_user_stats(user_id)
# Get actual memory count for this user
try:
user_memories = await mem0_manager.get_user_memories(user_id=user_id, limit=1000)
memory_count = len(user_memories)
except:
memory_count = 0
# Get relationship count for this user
try:
graph_data = await mem0_manager.get_graph_relationships(user_id=user_id)
relationship_count = len(graph_data.get('relationships', []))
except:
relationship_count = 0
return UserStatsResponse(
user_id=user_id,
memory_count=memory_count,
relationship_count=relationship_count,
last_activity=basic_stats['last_activity'],
api_calls_today=basic_stats['api_calls_today'],
avg_response_time_ms=basic_stats['avg_response_time_ms']
)
except Exception as e:
logger.error(f"Error getting user stats for {user_id}: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Utility endpoints
@app.get("/models")
async def get_available_models():
"""Get current model configuration."""
return {
"current_model": settings.default_model,
"endpoint": settings.openai_base_url,
"note": "Using single model with pure Mem0 intelligence"
}
@app.get("/users")
async def get_active_users():
"""Get list of users with memories (simplified implementation)."""
# This would typically query the database for users with memories
# For now, return a placeholder
return {
"message": "This endpoint would return users with stored memories",
"note": "Implementation depends on direct database access or Mem0 user enumeration capabilities"
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
log_level=settings.log_level.lower(),
reload=True
)

311
backend/mem0_manager.py Normal file
View file

@ -0,0 +1,311 @@
"""Ultra-minimal Mem0 Manager - Pure Mem0 + Custom OpenAI Endpoint Only."""
import logging
from typing import Dict, List, Optional, Any
from mem0 import Memory
from openai import OpenAI
from config import settings
from monitoring import timed
logger = logging.getLogger(__name__)
class Mem0Manager:
"""
Ultra-minimal manager that bridges custom OpenAI endpoint with pure Mem0.
No custom logic - let Mem0 handle all memory intelligence.
"""
def __init__(self):
# Custom endpoint configuration with graph memory enabled
config = {
"enable_graph": True,
"llm": {
"provider": "openai",
"config": {
"model": settings.default_model,
"api_key": settings.openai_api_key,
"openai_base_url": settings.openai_base_url
}
},
"embedder": {
"provider": "gemini",
"config": {
"model": "models/gemini-embedding-001",
"api_key": settings.embedder_api_key,
"embedding_dims": 1536
}
},
"vector_store": {
"provider": "pgvector",
"config": {
"dbname": settings.postgres_db,
"user": settings.postgres_user,
"password": settings.postgres_password,
"host": settings.postgres_host,
"port": settings.postgres_port,
"embedding_model_dims": 1536
}
},
"graph_store": {
"provider": "neo4j",
"config": {
"url": settings.neo4j_uri,
"username": settings.neo4j_username,
"password": settings.neo4j_password
}
},
}
self.memory = Memory.from_config(config)
self.openai_client = OpenAI(
api_key=settings.openai_api_key,
base_url=settings.openai_base_url
)
logger.info("Initialized ultra-minimal Mem0Manager with custom endpoint")
# Pure passthrough methods - no custom logic
@timed("add_memories")
async def add_memories(
self,
messages: List[Dict[str, str]],
user_id: str = "default",
agent_id: Optional[str] = None,
run_id: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Add memories - simplified native Mem0 pattern (10 lines vs 45)."""
try:
# Convert ChatMessage objects to dict if needed
formatted_messages = []
for msg in messages:
if hasattr(msg, 'dict'):
formatted_messages.append(msg.dict())
else:
formatted_messages.append(msg)
# Direct Mem0 add with metadata support
combined_metadata = metadata or {}
if agent_id:
combined_metadata["agent_id"] = agent_id
if run_id:
combined_metadata["run_id"] = run_id
result = self.memory.add(formatted_messages, user_id=user_id, metadata=combined_metadata if combined_metadata else None)
return {
"added_memories": result if isinstance(result, list) else [result],
"message": "Memories added successfully",
"hierarchy": {"user_id": user_id, "agent_id": agent_id, "run_id": run_id}
}
except Exception as e:
logger.error(f"Error adding memories: {e}")
raise e
@timed("search_memories")
async def search_memories(
self,
query: str,
user_id: str = "default",
limit: int = 5,
threshold: Optional[float] = None,
filters: Optional[Dict[str, Any]] = None,
keyword_search: bool = False,
rerank: bool = False,
filter_memories: bool = False,
agent_id: Optional[str] = None,
run_id: Optional[str] = None
) -> Dict[str, Any]:
"""Search memories - native Mem0 pattern (5 lines vs 70)."""
try:
# Minimal empty query protection for API compatibility
if not query or query.strip() == "":
return {"memories": [], "total_count": 0, "query": query, "note": "Empty query provided, no results returned. Use a specific query to search memories."}
# Direct Mem0 search - trust native handling
result = self.memory.search(query=query, user_id=user_id, limit=limit)
return {"memories": result.get("results", []), "total_count": len(result.get("results", [])), "query": query}
except Exception as e:
logger.error(f"Error searching memories: {e}")
raise e
async def get_user_memories(
self,
user_id: str,
limit: int = 10,
agent_id: Optional[str] = None,
run_id: Optional[str] = None,
filters: Optional[Dict[str, Any]] = None
) -> List[Dict[str, Any]]:
"""Get all memories for a user - native Mem0 pattern."""
try:
# Direct Mem0 get_all call - trust native parameter handling
result = self.memory.get_all(user_id=user_id, limit=limit)
return result.get("results", [])
except Exception as e:
logger.error(f"Error getting user memories: {e}")
raise e
@timed("update_memory")
async def update_memory(
self,
memory_id: str,
content: str,
) -> Dict[str, Any]:
"""Update memory - pure Mem0 passthrough."""
try:
result = self.memory.update(
memory_id=memory_id,
data=content
)
return {"message": "Memory updated successfully", "result": result}
except Exception as e:
logger.error(f"Error updating memory: {e}")
raise e
@timed("delete_memory")
async def delete_memory(self, memory_id: str) -> Dict[str, Any]:
"""Delete memory - pure Mem0 passthrough."""
try:
self.memory.delete(memory_id=memory_id)
return {"message": "Memory deleted successfully"}
except Exception as e:
logger.error(f"Error deleting memory: {e}")
raise e
async def delete_user_memories(self, user_id: str) -> Dict[str, Any]:
"""Delete all user memories - pure Mem0 passthrough."""
try:
self.memory.delete_all(user_id=user_id)
return {"message": "All user memories deleted successfully"}
except Exception as e:
logger.error(f"Error deleting user memories: {e}")
raise e
async def get_memory_history(self, memory_id: str) -> Dict[str, Any]:
"""Get memory change history - pure Mem0 passthrough."""
try:
history = self.memory.history(memory_id=memory_id)
return {
"memory_id": memory_id,
"history": history,
"message": "Memory history retrieved successfully"
}
except Exception as e:
logger.error(f"Error getting memory history: {e}")
raise e
async def get_graph_relationships(self, user_id: str) -> Dict[str, Any]:
"""Get graph relationships - using correct Mem0 get_all() method."""
try:
# Use get_all() to retrieve memories with graph relationships
result = self.memory.get_all(
user_id=user_id,
limit=50
)
# Extract relationships from Mem0's response structure
relationships = result.get("relations", [])
# For entities, we can derive them from memory results or relations
entities = []
if "results" in result:
# Extract unique entities from memories and relationships
entity_set = set()
# Add entities from relationships
for rel in relationships:
if "source" in rel:
entity_set.add(rel["source"])
if "target" in rel:
entity_set.add(rel["target"])
entities = [{"name": entity} for entity in entity_set]
return {
"relationships": relationships,
"entities": entities,
"user_id": user_id,
"total_memories": len(result.get("results", [])),
"total_relationships": len(relationships)
}
except Exception as e:
logger.error(f"Error getting graph relationships: {e}")
# Return empty but structured response on error
return {
"relationships": [],
"entities": [],
"user_id": user_id,
"total_memories": 0,
"total_relationships": 0,
"error": str(e)
}
@timed("chat_with_memory")
async def chat_with_memory(
self,
message: str,
user_id: str = "default",
context: Optional[List[Dict[str, str]]] = None,
metadata: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Chat with memory - native Mem0 pattern (15 lines vs 95)."""
try:
# Retrieve relevant memories using direct Mem0 search
search_result = self.memory.search(query=message, user_id=user_id, limit=3)
relevant_memories = search_result.get("results", [])
memories_str = "\n".join(f"- {entry['memory']}" for entry in relevant_memories)
# Generate Assistant response using Mem0's standard pattern
system_prompt = f"You are a helpful AI. Answer the question based on query and memories.\nUser Memories:\n{memories_str}"
messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": message}]
response = self.openai_client.chat.completions.create(model=settings.default_model, messages=messages)
assistant_response = response.choices[0].message.content
# Create new memories from the conversation
messages.append({"role": "assistant", "content": assistant_response})
self.memory.add(messages, user_id=user_id)
return {
"response": assistant_response,
"memories_used": len(relevant_memories),
"model_used": settings.default_model
}
except Exception as e:
logger.error(f"Error in chat_with_memory: {e}")
return {
"error": str(e),
"response": "I apologize, but I encountered an error processing your request.",
"memories_used": 0,
"model_used": None
}
async def health_check(self) -> Dict[str, str]:
"""Basic health check - just connectivity."""
status = {}
# Check custom OpenAI endpoint
try:
models = self.openai_client.models.list()
status["openai_endpoint"] = "healthy"
except Exception as e:
status["openai_endpoint"] = f"unhealthy: {str(e)}"
# Check Mem0 memory
try:
self.memory.search(query="test", user_id="health_check", limit=1)
status["mem0_memory"] = "healthy"
except Exception as e:
status["mem0_memory"] = f"unhealthy: {str(e)}"
return status
# Global instance
mem0_manager = Mem0Manager()

134
backend/models.py Normal file
View file

@ -0,0 +1,134 @@
"""Ultra-minimal Pydantic models for pure Mem0 API."""
from typing import List, Optional, Dict, Any
from pydantic import BaseModel, Field
# Request Models
class ChatMessage(BaseModel):
"""Chat message structure."""
role: str = Field(..., description="Message role (user, assistant, system)")
content: str = Field(..., description="Message content")
class ChatRequest(BaseModel):
"""Ultra-minimal chat request."""
message: str = Field(..., description="User message")
user_id: str = Field("default", description="User identifier")
context: Optional[List[ChatMessage]] = Field(None, description="Previous conversation context")
metadata: Optional[Dict[str, Any]] = Field(None, description="Additional metadata")
class MemoryAddRequest(BaseModel):
"""Request to add memories with hierarchy support - open-source compatible."""
messages: List[ChatMessage] = Field(..., description="Messages to process")
user_id: str = Field("default", description="User identifier")
agent_id: Optional[str] = Field(None, description="Agent identifier")
run_id: Optional[str] = Field(None, description="Run identifier")
metadata: Optional[Dict[str, Any]] = Field(None, description="Additional metadata")
class MemorySearchRequest(BaseModel):
"""Request to search memories with hierarchy filtering."""
query: str = Field(..., description="Search query")
user_id: str = Field("default", description="User identifier")
limit: int = Field(5, description="Maximum number of results")
threshold: Optional[float] = Field(None, description="Minimum relevance score")
filters: Optional[Dict[str, Any]] = Field(None, description="Additional filters")
# Hierarchy filters (open-source compatible)
agent_id: Optional[str] = Field(None, description="Filter by agent identifier")
run_id: Optional[str] = Field(None, description="Filter by run identifier")
class MemoryUpdateRequest(BaseModel):
"""Request to update a memory."""
memory_id: str = Field(..., description="Memory ID to update")
content: str = Field(..., description="New memory content")
metadata: Optional[Dict[str, Any]] = Field(None, description="Updated metadata")
# Response Models - Ultra-minimal
class MemoryItem(BaseModel):
"""Individual memory item."""
id: str = Field(..., description="Memory unique identifier")
memory: str = Field(..., description="Memory content")
user_id: Optional[str] = Field(None, description="Associated user ID")
metadata: Optional[Dict[str, Any]] = Field(None, description="Memory metadata")
score: Optional[float] = Field(None, description="Relevance score (for search results)")
created_at: Optional[str] = Field(None, description="Creation timestamp")
updated_at: Optional[str] = Field(None, description="Last update timestamp")
class MemorySearchResponse(BaseModel):
"""Memory search results - pure Mem0 structure."""
memories: List[MemoryItem] = Field(..., description="Found memories")
total_count: int = Field(..., description="Total number of memories found")
query: str = Field(..., description="Original search query")
class MemoryAddResponse(BaseModel):
"""Response from adding memories - pure Mem0 structure."""
added_memories: List[Dict[str, Any]] = Field(..., description="Memories that were added")
message: str = Field(..., description="Success message")
class GraphRelationship(BaseModel):
"""Graph relationship structure."""
source: str = Field(..., description="Source entity")
relationship: str = Field(..., description="Relationship type")
target: str = Field(..., description="Target entity")
properties: Optional[Dict[str, Any]] = Field(None, description="Relationship properties")
class GraphResponse(BaseModel):
"""Graph relationships - pure Mem0 structure."""
relationships: List[GraphRelationship] = Field(..., description="Found relationships")
entities: List[str] = Field(..., description="Unique entities")
user_id: str = Field(..., description="User identifier")
class HealthResponse(BaseModel):
"""Health check response."""
status: str = Field(..., description="Service status")
services: Dict[str, str] = Field(..., description="Individual service statuses")
timestamp: str = Field(..., description="Health check timestamp")
class ErrorResponse(BaseModel):
"""Error response structure."""
error: str = Field(..., description="Error message")
detail: Optional[str] = Field(None, description="Detailed error information")
status_code: int = Field(..., description="HTTP status code")
# Statistics and Monitoring Models
class MemoryOperationStats(BaseModel):
"""Memory operation statistics."""
add: int = Field(..., description="Number of add operations")
search: int = Field(..., description="Number of search operations")
update: int = Field(..., description="Number of update operations")
delete: int = Field(..., description="Number of delete operations")
class GlobalStatsResponse(BaseModel):
"""Global application statistics."""
total_memories: int = Field(..., description="Total memories across all users")
total_users: int = Field(..., description="Total number of users")
api_calls_today: int = Field(..., description="Total API calls today")
avg_response_time_ms: float = Field(..., description="Average response time in milliseconds")
memory_operations: MemoryOperationStats = Field(..., description="Memory operation breakdown")
uptime_seconds: float = Field(..., description="Application uptime in seconds")
class UserStatsResponse(BaseModel):
"""User-specific statistics."""
user_id: str = Field(..., description="User identifier")
memory_count: int = Field(..., description="Number of memories for this user")
relationship_count: int = Field(..., description="Number of graph relationships for this user")
last_activity: Optional[str] = Field(None, description="Last activity timestamp")
api_calls_today: int = Field(..., description="API calls made by this user today")
avg_response_time_ms: float = Field(..., description="Average response time for this user's requests")

205
backend/monitoring.py Normal file
View file

@ -0,0 +1,205 @@
"""Simple monitoring and statistics module for production debugging."""
import time
import uuid
import threading
from datetime import datetime, timezone
from typing import Dict, Any, Optional
from functools import wraps
from collections import defaultdict
import structlog
logger = structlog.get_logger(__name__)
class SimpleStats:
"""Thread-safe in-memory statistics storage."""
def __init__(self):
self._lock = threading.Lock()
self._start_time = time.time()
# Global counters
self._api_calls_today = 0
self._total_users = set()
self._memory_operations = defaultdict(int)
# Response time tracking
self._response_times = []
self._max_response_times = 1000 # Keep last 1000 measurements
# User-specific stats
self._user_stats = defaultdict(lambda: {
'api_calls_today': 0,
'response_times': [],
'last_activity': None
})
def record_api_call(self, user_id: Optional[str] = None, response_time_ms: float = 0):
"""Record an API call with timing."""
with self._lock:
self._api_calls_today += 1
# Track response times
self._response_times.append(response_time_ms)
if len(self._response_times) > self._max_response_times:
self._response_times.pop(0)
if user_id:
self._total_users.add(user_id)
user_data = self._user_stats[user_id]
user_data['api_calls_today'] += 1
user_data['response_times'].append(response_time_ms)
user_data['last_activity'] = datetime.now(timezone.utc).isoformat()
# Keep user response times bounded
if len(user_data['response_times']) > 100:
user_data['response_times'].pop(0)
def record_memory_operation(self, operation: str):
"""Record a memory operation (add, search, update, delete)."""
with self._lock:
self._memory_operations[operation] += 1
def get_global_stats(self) -> Dict[str, Any]:
"""Get global application statistics."""
with self._lock:
avg_response_time = sum(self._response_times) / len(self._response_times) if self._response_times else 0
uptime = time.time() - self._start_time
return {
'total_memories': 0, # Will be populated by actual Mem0 query
'total_users': len(self._total_users),
'api_calls_today': self._api_calls_today,
'avg_response_time_ms': round(avg_response_time, 2),
'memory_operations': {
'add': self._memory_operations['add'],
'search': self._memory_operations['search'],
'update': self._memory_operations['update'],
'delete': self._memory_operations['delete']
},
'uptime_seconds': round(uptime, 2)
}
def get_user_stats(self, user_id: str) -> Dict[str, Any]:
"""Get user-specific statistics."""
with self._lock:
user_data = self._user_stats[user_id]
avg_response_time = sum(user_data['response_times']) / len(user_data['response_times']) if user_data['response_times'] else 0
return {
'user_id': user_id,
'memory_count': 0, # Will be populated by actual Mem0 query
'relationship_count': 0, # Will be populated by actual Mem0 query
'last_activity': user_data['last_activity'],
'api_calls_today': user_data['api_calls_today'],
'avg_response_time_ms': round(avg_response_time, 2)
}
# Global statistics instance
stats = SimpleStats()
def generate_correlation_id() -> str:
"""Generate a unique correlation ID for request tracking."""
return str(uuid.uuid4())[:8]
def timed(operation_name: str):
"""Decorator to time function execution and log performance."""
def decorator(func):
@wraps(func)
async def async_wrapper(*args, **kwargs):
correlation_id = generate_correlation_id()
start_time = time.time()
# Log start of operation
logger.info(
f"Starting {operation_name}",
correlation_id=correlation_id,
operation=operation_name,
function=func.__name__
)
try:
result = await func(*args, **kwargs)
duration_ms = (time.time() - start_time) * 1000
# Log successful completion
logger.info(
f"Completed {operation_name}",
correlation_id=correlation_id,
operation=operation_name,
duration_ms=round(duration_ms, 2),
status="success"
)
# Record memory operations
if operation_name in ['add_memories', 'search_memories', 'update_memory', 'delete_memory']:
operation_type = operation_name.replace('_memories', '').replace('_memory', '')
stats.record_memory_operation(operation_type)
return result
except Exception as e:
duration_ms = (time.time() - start_time) * 1000
# Log error with context
logger.error(
f"Failed {operation_name}",
correlation_id=correlation_id,
operation=operation_name,
duration_ms=round(duration_ms, 2),
status="error",
error=str(e),
exc_info=True
)
raise
@wraps(func)
def sync_wrapper(*args, **kwargs):
correlation_id = generate_correlation_id()
start_time = time.time()
# Log start of operation
logger.info(
f"Starting {operation_name}",
correlation_id=correlation_id,
operation=operation_name,
function=func.__name__
)
try:
result = func(*args, **kwargs)
duration_ms = (time.time() - start_time) * 1000
# Log successful completion
logger.info(
f"Completed {operation_name}",
correlation_id=correlation_id,
operation=operation_name,
duration_ms=round(duration_ms, 2),
status="success"
)
return result
except Exception as e:
duration_ms = (time.time() - start_time) * 1000
# Log error with context
logger.error(
f"Failed {operation_name}",
correlation_id=correlation_id,
operation=operation_name,
duration_ms=round(duration_ms, 2),
status="error",
error=str(e),
exc_info=True
)
raise
# Return appropriate wrapper based on function type
if hasattr(func, '__code__') and func.__code__.co_flags & 0x80: # CO_COROUTINE
return async_wrapper
else:
return sync_wrapper
return decorator

33
backend/requirements.txt Normal file
View file

@ -0,0 +1,33 @@
# Core Framework
fastapi
uvicorn[standard]
python-multipart
# Mem0 and AI
mem0ai
openai
google-genai
# Database
psycopg2-binary
pgvector
neo4j
langchain-neo4j
rank-bm25
ollama
# Utilities
pydantic
pydantic-settings
python-dotenv
httpx
aiofiles
requests
# Logging and Monitoring
structlog
python-json-logger
# CORS and Security
python-jose[cryptography]
passlib[bcrypt]

66
config/postgres-init.sql Normal file
View file

@ -0,0 +1,66 @@
-- Initialize PostgreSQL database for Mem0 with pgvector extension
-- Create the pgvector extension for vector operations
CREATE EXTENSION IF NOT EXISTS vector;
-- Create user if not exists (in case it's needed)
DO
$do$
BEGIN
IF NOT EXISTS (
SELECT FROM pg_catalog.pg_roles
WHERE rolname = 'mem0_user') THEN
CREATE ROLE mem0_user LOGIN PASSWORD 'mem0_password';
END IF;
END
$do$;
-- Grant necessary permissions
GRANT ALL PRIVILEGES ON DATABASE mem0_db TO mem0_user;
GRANT ALL ON SCHEMA public TO mem0_user;
-- Create table for vector embeddings (if needed by Mem0's pgvector implementation)
CREATE TABLE IF NOT EXISTS embeddings (
id SERIAL PRIMARY KEY,
user_id VARCHAR(255),
content TEXT,
embedding VECTOR(1536), -- OpenAI embedding dimension
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
-- Create index for efficient vector similarity search
CREATE INDEX IF NOT EXISTS embeddings_embedding_idx ON embeddings
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
-- Create index for user_id lookups
CREATE INDEX IF NOT EXISTS embeddings_user_id_idx ON embeddings (user_id);
-- Create index for metadata queries
CREATE INDEX IF NOT EXISTS embeddings_metadata_idx ON embeddings USING GIN (metadata);
-- Grant permissions on the table
GRANT ALL PRIVILEGES ON TABLE embeddings TO mem0_user;
GRANT USAGE, SELECT ON SEQUENCE embeddings_id_seq TO mem0_user;
-- Create table for memory history tracking
CREATE TABLE IF NOT EXISTS memory_history (
id SERIAL PRIMARY KEY,
memory_id VARCHAR(255),
user_id VARCHAR(255),
action VARCHAR(50),
previous_value TEXT,
new_value TEXT,
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
-- Create indexes for memory history
CREATE INDEX IF NOT EXISTS memory_history_memory_id_idx ON memory_history (memory_id);
CREATE INDEX IF NOT EXISTS memory_history_user_id_idx ON memory_history (user_id);
CREATE INDEX IF NOT EXISTS memory_history_created_at_idx ON memory_history (created_at);
-- Grant permissions
GRANT ALL PRIVILEGES ON TABLE memory_history TO mem0_user;
GRANT USAGE, SELECT ON SEQUENCE memory_history_id_seq TO mem0_user;

97
docker-compose.yml Normal file
View file

@ -0,0 +1,97 @@
services:
# PostgreSQL with pgvector extension for vector storage
postgres:
image: pgvector/pgvector:pg15
container_name: mem0-postgres
environment:
POSTGRES_DB: ${POSTGRES_DB:-mem0_db}
POSTGRES_USER: ${POSTGRES_USER:-mem0_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mem0_password}
ports:
- "5433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./config/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mem0_user} -d ${POSTGRES_DB:-mem0_db}"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
# Neo4j with APOC for graph relationships
neo4j:
image: neo4j:5.18-community
container_name: mem0-neo4j
environment:
NEO4J_AUTH: ${NEO4J_AUTH:-neo4j/mem0_neo4j_password}
NEO4J_PLUGINS: '["apoc"]'
NEO4J_apoc_export_file_enabled: true
NEO4J_apoc_import_file_enabled: true
NEO4J_apoc_import_file_use__neo4j__config: true
NEO4J_ACCEPT_LICENSE_AGREEMENT: yes
NEO4J_dbms_security_procedures_unrestricted: apoc.*
NEO4J_dbms_security_procedures_allowlist: apoc.*
ports:
- "7474:7474" # HTTP
- "7687:7687" # Bolt
volumes:
- neo4j_data:/data
- neo4j_logs:/logs
- neo4j_import:/var/lib/neo4j/import
- neo4j_plugins:/plugins
healthcheck:
test: ["CMD", "cypher-shell", "-u", "neo4j", "-p", "${NEO4J_PASSWORD:-mem0_neo4j_password}", "RETURN 1"]
interval: 10s
timeout: 10s
retries: 5
restart: unless-stopped
# Backend API service
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: mem0-backend
environment:
OPENAI_API_KEY: ${OPENAI_COMPAT_API_KEY}
OPENAI_BASE_URL: https://veronica.pratikn.com/v1
EMBEDDER_API_KEY: ${EMBEDDER_API_KEY:-AIzaSyA_}
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_DB: ${POSTGRES_DB:-mem0_db}
POSTGRES_USER: ${POSTGRES_USER:-mem0_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mem0_password}
NEO4J_URI: bolt://neo4j:7687
NEO4J_USERNAME: ${NEO4J_USERNAME:-neo4j}
NEO4J_PASSWORD: ${NEO4J_PASSWORD:-mem0_neo4j_password}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000}
DEFAULT_MODEL: ${DEFAULT_MODEL:-claude-sonnet-4}
EXTRACTION_MODEL: ${EXTRACTION_MODEL:-o4-mini}
FAST_MODEL: ${FAST_MODEL:-o4-mini}
ANALYTICAL_MODEL: ${ANALYTICAL_MODEL:-gemini-2.5-pro}
REASONING_MODEL: ${REASONING_MODEL:-claude-sonnet-4}
EXPERT_MODEL: ${EXPERT_MODEL:-o3}
ports:
- "${BACKEND_PORT:-8000}:8000"
depends_on:
postgres:
condition: service_healthy
neo4j:
condition: service_healthy
restart: unless-stopped
volumes:
- ./backend:/app
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
volumes:
postgres_data:
neo4j_data:
neo4j_logs:
neo4j_import:
neo4j_plugins:
networks:
default:
name: mem0-network

View file

@ -0,0 +1,112 @@
# Mem0 API Reference - Add Memories
## Overview
The Add Memories API endpoint allows you to store memories in the Mem0 system. This endpoint processes messages and converts them into structured memories that can be later retrieved and used for contextual AI interactions.
## Endpoint
```
POST /v1/memories/
```
## Authentication
- **Authorization**: API key authentication required
- **Header**: `Authorization: Token your_api_key`
- **Format**: Prefix your Mem0 API key with 'Token '. Example: 'Token your_api_key'
## Python SDK Usage
```python
# To use the Python SDK, install the package:
# pip install mem0ai
from mem0 import MemoryClient
client = MemoryClient(api_key="your_api_key", org_id="your_org_id", project_id="your_project_id")
messages = [
{"role": "user", "content": "<user-message>"},
{"role": "assistant", "content": "<assistant-response>"}
]
client.add(messages, user_id="<user-id>", version="v2")
```
## Request Body Parameters
### Required Parameters
#### messages (object[])
An array of message objects representing the content of the memory. Each message object typically contains 'role' and 'content' fields, where 'role' indicates the sender either 'user' or 'assistant' and 'content' contains the actual message text. This structure allows for the representation of conversations or multi-part memories.
- **messages.{key}**: string | null
### Optional Parameters
#### Identifiers
- **agent_id** (string | null): The unique identifier of the agent associated with this memory
- **user_id** (string | null): The unique identifier of the user associated with this memory
- **app_id** (string | null): The unique identifier of the application associated with this memory
- **run_id** (string | null): The unique identifier of the run associated with this memory
- **org_id** (string | null): The unique identifier of the organization associated with this memory
- **project_id** (string | null): The unique identifier of the project associated with this memory
#### Configuration Options
- **metadata** (object | null): Additional metadata associated with the memory, which can be used to store any additional information or context about the memory. Best practice for incorporating additional information is through metadata (e.g. location, time, ids, etc.). During retrieval, you can either use these metadata alongside the query to fetch relevant memories or retrieve memories based on the query first and then refine the results using metadata during post-processing.
- **includes** (string | null): String to include the specific preferences in the memory. Minimum length: 1
- **excludes** (string | null): String to exclude the specific preferences in the memory. Minimum length: 1
- **infer** (boolean, default: true): Whether to infer the memories or directly store the messages
- **output_format** (string | null, default: v1.0): Output format options: `v1.0` (default) and `v1.1`. We recommend using `v1.1` as `v1.0` will be deprecated soon.
- **custom_categories** (object | null): A list of categories with category name and its description
- **custom_instructions** (string | null): Defines project-specific guidelines for handling and organizing memories. When set at the project level, they apply to all new memories in that project.
- **immutable** (boolean, default: false): Whether the memory is immutable
- **async_mode** (boolean, default: false): Whether to add the memory completely asynchronously
- **timestamp** (integer | null): The timestamp of the memory. Format: Unix timestamp
- **expiration_date** (string | null): The date and time when the memory will expire. Format: YYYY-MM-DD
- **version** (string | null): The version of the memory to use. The default version is v1, which is deprecated. We recommend using v2 for new applications.
## Response
### Success Response (200)
```json
[
{
"id": "<string>",
"data": {
"memory": "<string>"
},
"event": "ADD"
}
]
```
#### Response Fields
- **id** (string, required): Unique identifier for the created memory
- **data** (object, required): Contains the memory data
- **memory** (string, required): The processed memory content
- **event** (enum<string>, required): The type of operation performed
- Available options: `ADD`, `UPDATE`, `DELETE`
### Error Response (400)
Bad request - Invalid parameters or malformed request
## Best Practices
1. **Use Version v2**: The v1 API is deprecated. Always use `version="v2"` for new applications.
2. **Output Format**: Use `output_format="v1.1"` as the v1.0 format will be deprecated soon.
3. **Metadata Usage**: Store additional context information in metadata for better memory organization and retrieval.
4. **Message Structure**: Follow the standard conversation format with 'role' and 'content' fields for optimal memory processing.
5. **Async Mode**: Use async mode for bulk memory operations to improve performance.

View file

@ -0,0 +1,294 @@
# LlamaIndex ReAct Agent with Mem0
## Overview
A ReAct agent combines reasoning and action capabilities, making it versatile for tasks requiring both thought processes (reasoning) and interaction with tools or APIs (acting). Mem0 as memory enhances these capabilities by allowing the agent to store and retrieve contextual information from past interactions.
This guide demonstrates how to create a ReAct Agent with LlamaIndex that uses Mem0 as the memory store, showcasing the dramatic difference between agents with and without memory capabilities.
## Setup
### Installation
```bash
pip install llama-index-core llama-index-memory-mem0
```
### Initialize the LLM
```python
import os
from llama_index.llms.openai import OpenAI
os.environ["OPENAI_API_KEY"] = "<your-openai-api-key>"
llm = OpenAI(model="gpt-4o")
```
### Initialize Mem0 Memory
You can find your API key [here](https://app.mem0.ai/dashboard/api-keys). Read about Mem0 [Open Source](https://docs.mem0.ai/open-source/overview).
```python
os.environ["MEM0_API_KEY"] = "<your-mem0-api-key>"
from llama_index.memory.mem0 import Mem0Memory
context = {"user_id": "david"}
memory_from_client = Mem0Memory.from_client(
context=context,
api_key=os.environ["MEM0_API_KEY"],
search_msg_limit=4, # optional, default is 5
)
```
### Create Agent Tools
These tools will be used by the agent to perform actions:
```python
from llama_index.core.tools import FunctionTool
def call_fn(name: str):
"""Call the provided name.
Args:
name: str (Name of the person)
"""
return f"Calling... {name}"
def email_fn(name: str):
"""Email the provided name.
Args:
name: str (Name of the person)
"""
return f"Emailing... {name}"
def order_food(name: str, dish: str):
"""Order food for the provided name.
Args:
name: str (Name of the person)
dish: str (Name of the dish)
"""
return f"Ordering {dish} for {name}"
# Create tool instances
call_tool = FunctionTool.from_defaults(fn=call_fn)
email_tool = FunctionTool.from_defaults(fn=email_fn)
order_food_tool = FunctionTool.from_defaults(fn=order_food)
```
### Initialize the Agent with Memory
```python
from llama_index.core.agent import FunctionCallingAgent
agent = FunctionCallingAgent.from_tools(
[call_tool, email_tool, order_food_tool],
llm=llm,
memory=memory_from_client, # Mem0 memory integration
verbose=True,
)
```
## Building User Context
Let's start by having the agent learn about the user through conversation:
### Introduction
**Input:**
```python
response = agent.chat("Hi, My name is David")
print(response)
```
**Output:**
```
> Running step bf44a75a-a920-4cf3-944e-b6e6b5695043. Step input: Hi, My name is David
Added user message to memory: Hi, My name is David
=== LLM Response ===
Hello, David! How can I assist you today?
```
### Learning Preferences
**Input:**
```python
response = agent.chat("I love to eat pizza on weekends")
print(response)
```
**Output:**
```
> Running step 845783b0-b85b-487c-baee-8460ebe8b38d. Step input: I love to eat pizza on weekends
Added user message to memory: I love to eat pizza on weekends
=== LLM Response ===
Pizza is a great choice for the weekend! If you'd like, I can help you order some. Just let me know what kind of pizza you prefer!
```
### Communication Preferences
**Input:**
```python
response = agent.chat("My preferred way of communication is email")
print(response)
```
**Output:**
```
> Running step 345842f0-f8a0-42ea-a1b7-612265d72a92. Step input: My preferred way of communication is email
Added user message to memory: My preferred way of communication is email
=== LLM Response ===
Got it! If you need any assistance or have any requests, feel free to let me know, and I can communicate with you via email.
```
## Comparing Agents: With vs Without Memory
### Using the Agent WITHOUT Memory
**Setup:**
```python
agent_no_memory = FunctionCallingAgent.from_tools(
[call_tool, email_tool, order_food_tool],
# memory is not provided
llm=llm,
verbose=True,
)
```
**Input:**
```python
response = agent_no_memory.chat("I am feeling hungry, order me something and send me the bill")
print(response)
```
**Output:**
```
> Running step e89eb75d-75e1-4dea-a8c8-5c3d4b77882d. Step input: I am feeling hungry, order me something and send me the bill
Added user message to memory: I am feeling hungry, order me something and send me the bill
=== LLM Response ===
Please let me know your name and the dish you'd like to order, and I'll take care of it for you!
```
**Result:** The agent has no memory of previous conversations and cannot act on user preferences.
### Using the Agent WITH Memory
**Setup:**
```python
agent_with_memory = FunctionCallingAgent.from_tools(
[call_tool, email_tool, order_food_tool],
llm=llm,
memory=memory_from_client, # Mem0 memory integration
verbose=True,
)
```
**Input:**
```python
response = agent_with_memory.chat("I am feeling hungry, order me something and send me the bill")
print(response)
```
**Output:**
```
> Running step 5e473db9-3973-4cb1-a5fd-860be0ab0006. Step input: I am feeling hungry, order me something and send me the bill
Added user message to memory: I am feeling hungry, order me something and send me the bill
=== Calling Function ===
Calling function: order_food with args: {"name": "David", "dish": "pizza"}
=== Function Output ===
Ordering pizza for David
=== Calling Function ===
Calling function: email_fn with args: {"name": "David"}
=== Function Output ===
Emailing... David
> Running step 38080544-6b37-4bb2-aab2-7670100d926e. Step input: None
=== LLM Response ===
I've ordered a pizza for you, and the bill has been sent to your email. Enjoy your meal! If there's anything else you need, feel free to let me know.
```
**Result:** The agent remembers:
- User's name is David
- User loves pizza on weekends
- User prefers email communication
- Automatically orders pizza and sends bill via email
## Key Benefits of Memory Integration
### 1. **Personalized Responses**
- Agents remember user preferences and act accordingly
- No need to repeat information in every conversation
### 2. **Contextual Decision Making**
- Agents can make informed decisions based on past interactions
- Improved user experience through continuity
### 3. **Efficient Interactions**
- Reduced friction in user-agent communication
- Faster task completion with fewer prompts needed
### 4. **Learning and Adaptation**
- Agents improve over time by learning from interactions
- Better understanding of user behavior patterns
## Configuration Options
### Memory Configuration
```python
memory_from_client = Mem0Memory.from_client(
context=context,
api_key=os.environ["MEM0_API_KEY"],
search_msg_limit=4, # Controls how many past messages to retrieve
)
```
### Context Parameters
- **user_id**: Unique identifier for memory isolation between users
- **search_msg_limit**: Number of relevant past messages to include in context
## Use Cases
### 1. **Personal Assistants**
- Remember user preferences, schedules, and habits
- Provide personalized recommendations and actions
### 2. **Customer Support Agents**
- Maintain conversation history and user preferences
- Provide consistent support across multiple interactions
### 3. **E-commerce Assistants**
- Remember shopping preferences and past purchases
- Suggest relevant products and services
### 4. **Educational Tutors**
- Track learning progress and adapt teaching methods
- Remember student strengths and areas for improvement
## Best Practices
### 1. **Context Management**
- Use meaningful user IDs for proper memory isolation
- Adjust search_msg_limit based on conversation complexity
### 2. **Tool Design**
- Create tools that can leverage memory context
- Design functions with clear parameter definitions
### 3. **Memory Hygiene**
- Regularly review and update memory contents
- Implement privacy controls for sensitive information
### 4. **Testing**
- Test agents both with and without memory to understand impact
- Validate memory persistence across sessions
## Conclusion
Integrating Mem0 with LlamaIndex ReAct agents transforms static, forgetful assistants into intelligent, context-aware companions. The dramatic difference between agents with and without memory demonstrates the power of persistent context in creating truly helpful AI assistants.
The combination enables:
- **Continuity** across conversations
- **Personalization** based on user preferences
- **Efficiency** through reduced repetition
- **Intelligence** through accumulated context
This integration makes AI agents more human-like in their ability to remember and build upon past interactions, creating a foundation for truly intelligent and helpful AI systems.

View file

@ -0,0 +1,123 @@
# Running Mem0 Locally with Ollama
## Overview
Mem0 can be utilized entirely locally by leveraging Ollama for both the embedding model and the language model (LLM). This guide will walk you through the necessary steps and provide the complete code to get you started.
By using Ollama, you can run Mem0 locally, which allows for greater control over your data and models. This setup uses Ollama for both the embedding model and the language model, providing a fully local solution.
## Setup
Before you begin, ensure you have Mem0 and Ollama installed and properly configured on your local machine.
### Prerequisites
1. **Install Mem0**: `pip install mem0ai`
2. **Install and Configure Ollama**: Ensure Ollama is running on your local machine
3. **Install Qdrant**: Set up Qdrant as your vector store
4. **Pull Required Models**: Download the necessary Ollama models
## Full Code Example
Below is the complete code to set up and use Mem0 locally with Ollama:
```python
import os
from mem0 import Memory
config = {
"vector_store": {
"provider": "qdrant",
"config": {
"collection_name": "test",
"host": "localhost",
"port": 6333,
"embedding_model_dims": 768, # Change this according to your local model's dimensions
},
},
"llm": {
"provider": "ollama",
"config": {
"model": "llama3.1:latest",
"temperature": 0,
"max_tokens": 2000,
"ollama_base_url": "http://localhost:11434", # Ensure this URL is correct
},
},
"embedder": {
"provider": "ollama",
"config": {
"model": "nomic-embed-text:latest",
# Alternatively, you can use "snowflake-arctic-embed:latest"
"ollama_base_url": "http://localhost:11434",
},
},
}
# Initialize Memory with the configuration
m = Memory.from_config(config)
# Add a memory
m.add("I'm visiting Paris", user_id="john")
# Retrieve memories
memories = m.get_all(user_id="john")
```
## Configuration Details
### Vector Store Configuration
- **Provider**: Qdrant (local instance)
- **Host**: localhost
- **Port**: 6333
- **Collection**: test
- **Embedding Dimensions**: 768 (adjust based on your chosen embedding model)
### Language Model Configuration
- **Provider**: Ollama
- **Model**: llama3.1:latest
- **Temperature**: 0 (deterministic responses)
- **Max Tokens**: 2000
- **Base URL**: http://localhost:11434
### Embedding Model Configuration
- **Provider**: Ollama
- **Model**: nomic-embed-text:latest
- **Alternative**: snowflake-arctic-embed:latest
- **Base URL**: http://localhost:11434
## Key Points
1. **Configuration**: The setup involves configuring the vector store, language model, and embedding model to use local resources.
2. **Vector Store**: Qdrant is used as the vector store, running on localhost.
3. **Language Model**: Ollama is used as the LLM provider, with the "llama3.1:latest" model.
4. **Embedding Model**: Ollama is also used for embeddings, with the "nomic-embed-text:latest" model.
5. **Local Control**: This setup provides complete control over your data and models without external dependencies.
## Model Options
### Embedding Models
- **nomic-embed-text:latest**: Recommended for general text embedding
- **snowflake-arctic-embed:latest**: Alternative embedding model
### Language Models
- **llama3.1:latest**: Current recommended model
- Other Ollama-supported models can be used based on your requirements
## Benefits of Local Setup
1. **Data Privacy**: All data processing happens locally
2. **No API Costs**: No external API usage fees
3. **Offline Operation**: Works without internet connectivity
4. **Custom Models**: Use any Ollama-supported models
5. **Full Control**: Complete control over model parameters and data flow
## Conclusion
This local setup of Mem0 using Ollama provides a fully self-contained solution for memory management and AI interactions. It allows for greater control over your data and models while still leveraging the powerful capabilities of Mem0.
The combination of Mem0's memory management capabilities with Ollama's local AI models creates a powerful, privacy-focused solution for building AI applications with persistent memory.

View file

@ -0,0 +1,204 @@
# Personalized Deep Research with Mem0
## Overview
Deep Research is an intelligent agent that synthesizes large amounts of online data and completes complex research tasks, customized to your unique preferences and insights. Built on Mem0's technology, it enhances AI-driven online exploration with personalized memories.
Deep Research leverages Mem0's memory capabilities to:
- **Synthesize large amounts of online data** from multiple sources
- **Complete complex research tasks** with intelligent analysis
- **Customize results to your preferences** based on your background and expertise
- **Store and utilize personal insights** for continuous learning
- **Maintain context across research sessions** for ongoing projects
## Demo
Watch Deep Research in action through this demonstration video that showcases the platform's capabilities in real-time research scenarios.
**Video**: [Deepresearch Demo - YouTube](https://www.youtube.com/watch?v=8vQlCtXzF60)
The demo illustrates how the system:
- Processes complex research queries
- Synthesizes information from multiple sources
- Provides personalized insights based on user context
- Maintains research continuity across sessions
## Key Features
### 1. Personalized Research
**Intelligent Customization:**
- **Analyzes your background and expertise** to understand your knowledge level
- **Tailors research depth and complexity** to match your understanding
- **Incorporates your previous research context** for continuity
- **Adapts research methodology** based on your preferences and past queries
**Benefits:**
- No more generic, one-size-fits-all research results
- Research that builds upon your existing knowledge
- Tailored complexity levels for different expertise areas
- Context-aware recommendations and insights
### 2. Comprehensive Data Synthesis
**Advanced Information Processing:**
- **Processes multiple online sources** simultaneously
- **Extracts relevant information** using intelligent filtering
- **Provides coherent summaries** with key insights highlighted
- **Cross-references information** for accuracy and completeness
**Data Sources:**
- Academic papers and journals
- News articles and reports
- Industry publications
- Technical documentation
- Expert opinions and analysis
### 3. Memory Integration
**Persistent Knowledge Management:**
- **Stores research findings** for future reference and building upon
- **Maintains context across sessions** for long-term research projects
- **Links related research topics** to create knowledge networks
- **Tracks research evolution** and builds upon previous insights
**Memory Capabilities:**
- Research topic associations
- Source credibility tracking
- Personal insight storage
- Query history and refinements
### 4. Interactive Exploration
**Dynamic Research Experience:**
- **Allows real-time query refinement** based on initial results
- **Supports follow-up questions** for deeper investigation
- **Enables deep-diving into specific areas** of interest
- **Provides research path recommendations** for comprehensive coverage
**Interactive Features:**
- Query suggestion and refinement
- Related topic exploration
- Source verification and cross-checking
- Research methodology adaptation
## Use Cases
### Academic Research
- **Literature Reviews**: Comprehensive analysis of existing research in your field
- **Thesis Research**: In-depth investigation for academic writing
- **Paper Writing**: Supporting evidence and citation gathering
- **Grant Proposals**: Background research and supporting documentation
**Example**: A PhD student researching "Machine Learning in Healthcare" gets personalized results based on their computer science background, with technical depth appropriate for their expertise level.
### Market Research
- **Industry Analysis**: Comprehensive market landscape evaluation
- **Competitor Research**: Detailed competitive intelligence gathering
- **Trend Identification**: Emerging patterns and future predictions
- **Customer Insights**: Behavior analysis and preference research
**Example**: A startup founder researching "SaaS Market Trends 2024" receives results tailored to their B2B software background and current market focus.
### Technical Research
- **Technology Evaluation**: Comprehensive assessment of technical solutions
- **Solution Comparison**: Detailed analysis of alternatives
- **Implementation Research**: Best practices and case studies
- **Performance Analysis**: Benchmarks and optimization strategies
**Example**: A software architect researching "Microservices vs Monolith" gets results customized to their specific tech stack and project requirements.
### Business Research
- **Strategic Planning**: Market opportunities and competitive positioning
- **Opportunity Analysis**: Investment and expansion research
- **Risk Assessment**: Comprehensive risk evaluation and mitigation
- **Partnership Research**: Potential collaborator and vendor analysis
**Example**: A business analyst researching "Digital Transformation Trends" receives insights tailored to their industry and organizational context.
## Technical Advantages
### Memory-Powered Intelligence
- **Contextual Understanding**: Builds upon previous research sessions
- **Personal Preference Learning**: Adapts to your research style and interests
- **Knowledge Graph Building**: Creates connections between research topics
- **Expertise Level Adaptation**: Matches complexity to your background
### Data Quality and Accuracy
- **Source Verification**: Multiple source cross-referencing
- **Credibility Assessment**: Automatic source reliability evaluation
- **Information Synthesis**: Intelligent combination of multiple perspectives
- **Bias Detection**: Identification of potential information bias
### Efficiency and Productivity
- **Time Savings**: Automated information gathering and synthesis
- **Relevant Filtering**: Focus on information most relevant to your needs
- **Continuous Learning**: Improves recommendations over time
- **Session Continuity**: Pick up where you left off in previous sessions
## Getting Started
### Installation and Setup
To try the Personalized Deep Research system yourself:
1. **Clone the Repository**:
```bash
git clone https://github.com/mem0ai/personalized-deep-research.git
cd personalized-deep-research
git checkout mem0
```
2. **Follow Setup Instructions**:
- Review the README file for detailed installation instructions
- Configure your API keys and environment variables
- Set up your Mem0 integration for memory capabilities
3. **Local Deployment**:
- Run the application locally for testing and development
- Customize the system based on your specific research needs
4. **Production Deployment**:
- Deploy to your preferred cloud platform
- Scale according to your research volume requirements
### Configuration Options
- **Research Domains**: Customize for specific fields of interest
- **Source Preferences**: Configure preferred information sources
- **Depth Settings**: Adjust research comprehensiveness levels
- **Memory Settings**: Configure memory retention and association rules
## Benefits Summary
### For Researchers
- **Personalized Results**: Research tailored to your expertise and interests
- **Time Efficiency**: Faster information gathering and synthesis
- **Context Continuity**: Building upon previous research sessions
- **Quality Insights**: Comprehensive and credible information sources
### For Organizations
- **Knowledge Management**: Centralized research and insights storage
- **Team Collaboration**: Shared research context and findings
- **Strategic Intelligence**: Informed decision-making support
- **Competitive Advantage**: Superior research capabilities
## Repository and Resources
**GitHub Repository**: [Personalized Deep Research](https://github.com/mem0ai/personalized-deep-research/tree/mem0)
The repository includes:
- Complete source code and implementation
- Setup and deployment instructions
- Configuration examples and templates
- Documentation and usage guides
- Example research scenarios and outputs
## Conclusion
Personalized Deep Research represents the next evolution in AI-powered research tools, combining the comprehensive data synthesis capabilities of modern AI with the personalized, context-aware features enabled by Mem0's memory technology.
This approach transforms research from a repetitive, generic process into an intelligent, personalized experience that learns and adapts to your specific needs, expertise, and research goals. Whether you're conducting academic research, market analysis, or technical evaluation, the system provides tailored insights that build upon your existing knowledge and research context.
The integration of memory capabilities ensures that each research session contributes to a growing knowledge base, making future research more efficient and insightful while maintaining the context and continuity essential for complex, long-term research projects.

View file

@ -0,0 +1,227 @@
# Personalized Search with Mem0 and Tavily
## Overview
Imagine asking a search assistant for "coffee shops nearby" and instead of generic results, it shows remote-work-friendly cafes with great wifi in your city because it remembers you mentioned working remotely before. Or when you search for "lunchbox ideas for kids" it knows you have a **7-year-old daughter** and recommends **peanut-free options** that align with her allergy.
That's what we are going to build today, a **Personalized Search Assistant** powered by **Mem0** for memory and [Tavily](https://tavily.com/) for real-time search.
## Why Personalized Search
Most assistants treat every query like they've never seen you before. That means repeating yourself about your location, diet, or preferences, and getting results that feel generic.
- With **Mem0**, your assistant builds a memory of the user's world.
- With **Tavily**, it fetches fresh and accurate results in real time.
Together, they make every interaction **smarter, faster, and more personal**.
## Prerequisites
Before you begin, make sure you have:
1. **Install the dependencies:**
```bash
pip install langchain mem0ai langchain-tavily langchain-openai
```
2. **Set up your API keys** in a .env file:
```bash
OPENAI_API_KEY=your-openai-key
TAVILY_API_KEY=your-tavily-key
MEM0_API_KEY=your-mem0-key
```
## Code Walkthrough
Let's break down the main components.
### 1: Initialize Mem0 with Custom Instructions
We configure Mem0 with custom instructions that guide it to infer user memories tailored specifically for our usecase.
```python
from mem0 import MemoryClient
mem0_client = MemoryClient()
mem0_client.project.update(
custom_instructions='''
INFER THE MEMORIES FROM USER QUERIES EVEN IF IT'S A QUESTION.
We are building personalized search for which we need to understand about user's preferences and life
and extract facts and memories accordingly.
'''
)
```
Now, if a user casually mentions "I need to pick up my daughter", or "What's the weather at Los Angeles", Mem0 remembers they have a daughter or user is somewhat interested/connected with Los Angeles in terms of location, those will be referred for future searches.
### 2. Simulating User History
To test personalization, we preload some sample conversation history for a user:
```python
def setup_user_history(user_id):
conversations = [
[{"role": "user", "content": "What will be the weather today at Los Angeles? I need to pick up my daughter from office."},
{"role": "assistant", "content": "I'll check the weather in LA for you."}],
[{"role": "user", "content": "I'm looking for vegan restaurants in Santa Monica"},
{"role": "assistant", "content": "I'll find great vegan options in Santa Monica."}],
[{"role": "user", "content": "My 7-year-old daughter is allergic to peanuts"},
{"role": "assistant", "content": "I'll remember to check for peanut-free options."}],
[{"role": "user", "content": "I work remotely and need coffee shops with good wifi"},
{"role": "assistant", "content": "I'll find remote-work-friendly coffee shops."}],
[{"role": "user", "content": "We love hiking and outdoor activities on weekends"},
{"role": "assistant", "content": "Great! I'll keep your outdoor activity preferences in mind."}],
]
for conversation in conversations:
mem0_client.add(conversation, user_id=user_id, output_format="v1.1")
```
This gives the agent a baseline understanding of the user's lifestyle and needs.
### 3. Retrieving User Context from Memory
When a user makes a new search query, we retrieve relevant memories to enhance the search query:
```python
def get_user_context(user_id, query):
filters = {"AND": [{"user_id": user_id}]}
user_memories = mem0_client.search(query=query, version="v2", filters=filters)
if user_memories:
context = "\n".join([f"- {memory['memory']}" for memory in user_memories])
return context
else:
return "No previous user context available."
```
This context is injected into the search agent so results are personalized.
### 4. Creating the Personalized Search Agent
The agent uses Tavily search, but always augments search queries with user context:
```python
def create_personalized_search_agent(user_context):
tavily_search = TavilySearch(
max_results=10,
search_depth="advanced",
include_answer=True,
topic="general"
)
tools = [tavily_search]
prompt = ChatPromptTemplate.from_messages([
("system", f"""You are a personalized search assistant.
USER CONTEXT AND PREFERENCES:
{user_context}
YOUR ROLE:
1. Analyze the user's query and context.
2. Enhance the query with relevant personal memories.
3. Always use tavily_search for results.
4. Explain which memories influenced personalization.
"""),
MessagesPlaceholder(variable_name="messages"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
agent = create_openai_tools_agent(llm=llm, tools=tools, prompt=prompt)
return AgentExecutor(agent=agent, tools=tools, verbose=True, return_intermediate_steps=True)
```
### 5. Run a Personalized Search
The workflow ties everything together:
```python
def conduct_personalized_search(user_id, query):
user_context = get_user_context(user_id, query)
agent_executor = create_personalized_search_agent(user_context)
response = agent_executor.invoke({"messages": [HumanMessage(content=query)]})
return {"agent_response": response['output']}
```
### 6. Store New Interactions
Every new query/response pair is stored for future personalization:
```python
def store_search_interaction(user_id, original_query, agent_response):
interaction = [
{"role": "user", "content": f"Searched for: {original_query}"},
{"role": "assistant", "content": f"Results based on preferences: {agent_response}"}
]
mem0_client.add(messages=interaction, user_id=user_id, output_format="v1.1")
```
### Full Example Run
```python
if __name__ == "__main__":
user_id = "john"
setup_user_history(user_id)
queries = [
"good coffee shops nearby for working",
"what can I make for my kid in lunch?"
]
for q in queries:
results = conduct_personalized_search(user_id, q)
print(f"\nQuery: {q}")
print(f"Personalized Response: {results['agent_response']}")
```
## How It Works in Practice
Here's how personalization plays out:
1. **Context Gathering**: User previously mentioned living in Los Angeles, being vegan, and having a 7-year-old daughter allergic to peanuts.
2. **Enhanced Search Query**:
- **Original Query**: "good coffee shops nearby for working"
- **Enhanced Query**: "good coffee shops in Los Angeles with strong wifi, remote-work-friendly"
3. **Personalized Results**: The assistant only returns wifi-friendly, work-friendly cafes near Los Angeles.
4. **Memory Update**: Interaction is saved for better future recommendations.
## Key Benefits
1. **Context Awareness**: Remembers user preferences and personal details
2. **Intelligent Query Enhancement**: Automatically improves search queries with personal context
3. **Continuous Learning**: Gets better with each interaction
4. **Real-time Results**: Combines memory with fresh search data
5. **Privacy-Focused**: Personal data stays within the Mem0 system
## Use Cases
- **Shopping Assistants**: Remember dietary restrictions, sizes, and brand preferences
- **Travel Planning**: Recall budget constraints, accessibility needs, and past preferences
- **Content Discovery**: Suggest articles, videos, or products based on interests
- **Local Services**: Find services that match past requirements and preferences
- **Health & Wellness**: Remember health conditions, allergies, and fitness goals
## Security Compliance
🔐 **Mem0 is now SOC 2 and HIPAA compliant!** We're committed to the highest standards of data security and privacy, enabling secure memory for enterprises, healthcare, and beyond.
## Conclusion
With Mem0 + Tavily, you can build a search assistant that doesn't just fetch results but understands the person behind the query. Whether for shopping, travel, or daily life, this approach turns a generic search into a truly personalized experience.
**Full Code**: [Personalized Search GitHub](https://github.com/mem0ai/mem0/blob/main/examples/misc/personalized_search.py)
## Next Steps
1. **Experiment with different search domains** (shopping, travel, local services)
2. **Add more sophisticated memory categorization** for better context retrieval
3. **Implement user feedback loops** to improve memory accuracy
4. **Scale to multiple users** with proper data isolation
5. **Add privacy controls** for memory management and deletion

6
docs/memory.md Normal file
View file

@ -0,0 +1,6 @@
https://docs.mem0.ai/api-reference/memory/add-memories
https://docs.mem0.ai/examples/mem0-with-ollama
https://docs.mem0.ai/examples/personalized-search-tavily-mem0
https://docs.mem0.ai/examples/llama-index-mem0
https://docs.mem0.ai/examples/personalized-deep-research
https://docs.mem0.ai/open-source/graph_memory/overview

View file

@ -0,0 +1,403 @@
# Mem0 Open Source - Graph Memory Overview
## Introduction
Mem0 now supports **Graph Memory** capabilities that enable users to create and utilize complex relationships between pieces of information, allowing for more nuanced and context-aware responses. This integration combines the strengths of both vector-based and graph-based approaches, resulting in more accurate and comprehensive information retrieval and generation.
**NodeSDK now supports Graph Memory** 🎉
## Installation
To use Mem0 with Graph Memory support, install it using pip:
### Python
```bash
pip install "mem0ai[graph]"
```
### TypeScript
The NodeSDK includes graph memory support in the standard installation.
This command installs Mem0 along with the necessary dependencies for graph functionality.
**Try Graph Memory on Google Colab**: [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1PfIGVHnliIlG2v8cx0g45TF0US-jRPZ1?usp=sharing)
**Demo Video**: [Dynamic Graph Memory by Mem0 - YouTube](https://www.youtube.com/watch?v=u_ZAqNNVtXA)
## Initialize Graph Memory
To initialize Graph Memory, you'll need to set up your configuration with graph store providers. Currently, we support **Neo4j**, **Memgraph**, and **Neptune Analytics** as graph store providers.
### Initialize Neo4j
You can setup [Neo4j](https://neo4j.com/) locally or use the hosted [Neo4j AuraDB](https://neo4j.com/product/auradb/).
**Important**: If you are using Neo4j locally, you need to install [APOC plugins](https://neo4j.com/labs/apoc/4.1/installation/).
#### LLM Configuration Options
Users can customize the LLM for Graph Memory from the [Supported LLM list](https://docs.mem0.ai/components/llms/overview) with three levels of configuration:
1. **Main Configuration**: If `llm` is set in the main config, it will be used for all graph operations.
2. **Graph Store Configuration**: If `llm` is set in the graph_store config, it will override the main config `llm` and be used specifically for graph operations.
3. **Default Configuration**: If no custom LLM is set, the default LLM (`gpt-4o-2024-08-06`) will be used for all graph operations.
#### Python Configuration
```python
from mem0 import Memory
config = {
"graph_store": {
"provider": "neo4j",
"config": {
"url": "neo4j+s://xxx",
"username": "neo4j",
"password": "xxx"
}
}
}
m = Memory.from_config(config_dict=config)
```
#### TypeScript Configuration
If you are using NodeSDK, you need to pass `enableGraph` as `true` in the `config` object.
```typescript
import { Memory } from "mem0ai/oss";
const config = {
enableGraph: true,
graphStore: {
provider: "neo4j",
config: {
url: "neo4j+s://xxx",
username: "neo4j",
password: "xxx",
}
}
}
const memory = new Memory(config);
```
### Initialize Memgraph
Run Memgraph with Docker:
```bash
docker run -p 7687:7687 memgraph/memgraph-mage:latest --schema-info-enabled=True
```
The `--schema-info-enabled` flag is set to `True` for more performant schema generation.
**Additional Information**: [Memgraph documentation](https://memgraph.com/docs)
#### Python Configuration
```python
from mem0 import Memory
config = {
"graph_store": {
"provider": "memgraph",
"config": {
"url": "bolt://localhost:7687",
"username": "memgraph",
"password": "xxx",
},
},
}
m = Memory.from_config(config_dict=config)
```
### Initialize Neptune Analytics
Mem0 now supports Amazon Neptune Analytics as a graph store provider. This integration allows you to use Neptune Analytics for storing and querying graph-based memories.
#### Instance Setup
1. Create an Amazon Neptune Analytics instance in your AWS account following the [AWS documentation](https://docs.aws.amazon.com/neptune-analytics/latest/userguide/get-started.html).
2. **Important Considerations**:
- Public connectivity is not enabled by default, and if accessing from outside a VPC, it needs to be enabled.
- Once the Amazon Neptune Analytics instance is available, you will need the graph-identifier to connect.
- The Neptune Analytics instance must be created using the same vector dimensions as the embedding model creates. See: [Vector Index Documentation](https://docs.aws.amazon.com/neptune-analytics/latest/userguide/vector-index.html)
#### Attach Credentials
Configure your AWS credentials with access to your Amazon Neptune Analytics resources by following the [Configuration and credentials precedence](https://docs.aws.amazon.com/cli/v1/userguide/cli-chap-configure.html#configure-precedence).
**Environment Variables Example**:
```bash
export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key
export AWS_SESSION_TOKEN=your-session-token
export AWS_DEFAULT_REGION=your-region
```
**Required IAM Permissions**: The IAM user or role making the request must have a policy attached that allows one of the following IAM actions in that neptune-graph:
- neptune-graph:ReadDataViaQuery
- neptune-graph:WriteDataViaQuery
- neptune-graph:DeleteDataViaQuery
#### Usage
```python
from mem0 import Memory
# This example must connect to a neptune-graph instance with 1536 vector dimensions specified.
config = {
"embedder": {
"provider": "openai",
"config": {"model": "text-embedding-3-large", "embedding_dims": 1536},
},
"graph_store": {
"provider": "neptune",
"config": {
"endpoint": "neptune-graph://<GRAPH_ID>",
},
},
}
m = Memory.from_config(config_dict=config)
```
#### Troubleshooting
- **Connection Issues**: Refer to the [Connecting to a graph guide](https://docs.aws.amazon.com/neptune-analytics/latest/userguide/gettingStarted-connecting.html)
- **Authentication Issues**: Refer to the [boto3 client configuration options](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html)
- **Detailed Examples**: See the [Neptune Analytics example notebook](https://docs.mem0.ai/open-source/graph_memory/examples/graph-db-demo/neptune-analytics-example.ipynb)
## Graph Operations
Mem0's graph supports the following core operations:
### Add Memories
Mem0 with Graph Memory supports both "user_id" and "agent_id" parameters. You can use either or both to organize your memories.
#### Python
```python
# Using only user_id
m.add("I like pizza", user_id="alice")
# Using both user_id and agent_id
m.add("I like pizza", user_id="alice", agent_id="food-assistant")
```
#### TypeScript
```typescript
// Using only userId
await memory.add("I like pizza", { userId: "alice" });
// Using both userId and agentId
await memory.add("I like pizza", { userId: "alice", agentId: "food-assistant" });
```
### Get All Memories
#### Python
```python
# Get all memories for a user
m.get_all(user_id="alice")
# Get all memories for a specific agent belonging to a user
m.get_all(user_id="alice", agent_id="food-assistant")
```
#### TypeScript
```typescript
// Get all memories for a user
await memory.getAll({ userId: "alice" });
// Get all memories for a specific agent belonging to a user
await memory.getAll({ userId: "alice", agentId: "food-assistant" });
```
### Search Memories
#### Python
```python
# Search memories for a user
m.search("tell me my name.", user_id="alice")
# Search memories for a specific agent belonging to a user
m.search("tell me my name.", user_id="alice", agent_id="food-assistant")
```
#### TypeScript
```typescript
// Search memories for a user
await memory.search("tell me my name.", { userId: "alice" });
// Search memories for a specific agent belonging to a user
await memory.search("tell me my name.", { userId: "alice", agentId: "food-assistant" });
```
### Delete All Memories
#### Python
```python
# Delete all memories for a user
m.delete_all(user_id="alice")
# Delete all memories for a specific agent belonging to a user
m.delete_all(user_id="alice", agent_id="food-assistant")
```
#### TypeScript
```typescript
// Delete all memories for a user
await memory.deleteAll({ userId: "alice" });
// Delete all memories for a specific agent belonging to a user
await memory.deleteAll({ userId: "alice", agentId: "food-assistant" });
```
## Example Usage
Here's a comprehensive example of how to use Mem0's graph operations:
1. **First**, we'll add some memories for a user named Alice.
2. **Then**, we'll visualize how the graph evolves as we add more memories.
3. **You'll see** how entities and relationships are automatically extracted and connected in the graph.
### Step-by-Step Memory Addition
#### 1. Add memory 'I like going to hikes'
```python
m.add("I like going to hikes", user_id="alice123")
```
**Result**: Creates initial user node and hiking preference relationship.
#### 2. Add memory 'I love to play badminton'
```python
m.add("I love to play badminton", user_id="alice123")
```
**Result**: Adds badminton activity and positive relationship.
#### 3. Add memory 'I hate playing badminton'
```python
m.add("I hate playing badminton", user_id="alice123")
```
**Result**: Updates existing badminton relationship, showing preference conflict resolution.
#### 4. Add memory 'My friend name is john and john has a dog named tommy'
```python
m.add("My friend name is john and john has a dog named tommy", user_id="alice123")
```
**Result**: Creates complex relationship network: Alice -> friends with -> John -> owns -> Tommy (dog).
#### 5. Add memory 'My name is Alice'
```python
m.add("My name is Alice", user_id="alice123")
```
**Result**: Adds identity information to the user node.
#### 6. Add memory 'John loves to hike and Harry loves to hike as well'
```python
m.add("John loves to hike and Harry loves to hike as well", user_id="alice123")
```
**Result**: Creates connections between John, Harry, and hiking activities, potentially connecting with Alice's hiking preference.
#### 7. Add memory 'My friend peter is the spiderman'
```python
m.add("My friend peter is the spiderman", user_id="alice123")
```
**Result**: Adds another friend relationship with identity/role information.
### Search Examples
#### Search for Identity
```python
result = m.search("What is my name?", user_id="alice123")
```
**Expected Response**: Returns Alice's name and related identity information from the graph.
#### Search for Relationships
```python
result = m.search("Who is spiderman?", user_id="alice123")
```
**Expected Response**: Returns Peter and his spiderman identity, along with his friendship relationship to Alice.
## Using Multiple Agents with Graph Memory
When working with multiple agents, you can use the "agent_id" parameter to organize memories by both user and agent. This allows you to:
1. **Create agent-specific knowledge graphs**
2. **Share common knowledge between agents**
3. **Isolate sensitive or specialized information to specific agents**
### Example: Multi-Agent Setup
```python
# Add memories for different agents
m.add("I prefer Italian cuisine", user_id="bob", agent_id="food-assistant")
m.add("I'm allergic to peanuts", user_id="bob", agent_id="health-assistant")
m.add("I live in Seattle", user_id="bob") # Shared across all agents
# Search within specific agent context
food_preferences = m.search("What food do I like?", user_id="bob", agent_id="food-assistant")
health_info = m.search("What are my allergies?", user_id="bob", agent_id="health-assistant")
location = m.search("Where do I live?", user_id="bob") # Searches across all agents
```
### Agent Isolation Benefits
1. **Privacy**: Sensitive health information stays with health-related agents
2. **Specialization**: Each agent builds domain-specific knowledge
3. **Shared Context**: Common information (like location) remains accessible to all agents
4. **Scalability**: Easy to add new agents without disrupting existing knowledge
## Key Features
### Automatic Entity Extraction
- **Smart Recognition**: Automatically identifies people, places, objects, and concepts
- **Relationship Mapping**: Creates meaningful connections between entities
- **Context Preservation**: Maintains semantic relationships in the graph
### Dynamic Graph Evolution
- **Real-time Updates**: Graph structure evolves as new memories are added
- **Conflict Resolution**: Handles contradictory information intelligently
- **Relationship Strengthening**: Reinforces connections through repeated mentions
### Intelligent Querying
- **Contextual Search**: Searches consider relationship context, not just semantic similarity
- **Graph Traversal**: Finds information through relationship paths
- **Multi-hop Reasoning**: Can answer questions requiring connection of multiple entities
### Hybrid Architecture
- **Vector + Graph**: Combines semantic search with relationship reasoning
- **Dual Storage**: Information stored in both vector and graph formats
- **Unified Interface**: Single API for both vector and graph operations
## Important Notes
> **Note**: The Graph Memory implementation is not standalone. You will be adding/retrieving memories to the vector store and the graph store simultaneously.
This hybrid approach ensures:
- **Semantic Search**: Vector storage enables similarity-based retrieval
- **Relationship Reasoning**: Graph storage enables connection-based queries
- **Comprehensive Results**: Queries leverage both approaches for better accuracy
## Getting Help
If you want to use a managed version of Mem0, please check out [Mem0 Platform](https://mem0.dev/pd). If you have any questions, please feel free to reach out to us using one of the following methods:
- **[Discord](https://mem0.dev/DiD)**: Join our community
- **[GitHub](https://github.com/mem0ai/mem0/discussions/new?category=q-a)**: Ask questions on GitHub
- **[Support](https://cal.com/taranjeetio/meet)**: Talk to founders
## Conclusion
Graph Memory in Mem0 represents a significant advancement in AI memory capabilities, enabling more sophisticated reasoning and context-aware responses. By combining the semantic understanding of vector databases with the relationship intelligence of graph databases, Mem0 provides a comprehensive solution for building truly intelligent AI systems.
The ability to automatically extract entities, create relationships, and reason across connections makes Graph Memory particularly powerful for:
- **Personal AI Assistants** that understand complex personal relationships
- **Customer Support Systems** that maintain comprehensive user context
- **Knowledge Management Platforms** that connect related information
- **Multi-Agent Systems** that share and specialize knowledge appropriately
With support for multiple graph database backends and both Python and TypeScript SDKs, Graph Memory provides the flexibility and scalability needed for production AI applications.

44
frontend/package.json Normal file
View file

@ -0,0 +1,44 @@
{
"name": "mem0-interface-frontend",
"version": "1.0.0",
"description": "React frontend for Mem0 Interface POC",
"main": "src/index.js",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"axios": "^1.6.0",
"react-router-dom": "^6.8.0",
"@mui/material": "^5.14.0",
"@mui/icons-material": "^5.14.0",
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"recharts": "^2.8.0",
"react-json-view": "^1.21.3"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"proxy": "http://localhost:8000"
}

543
test_integration.py Normal file
View file

@ -0,0 +1,543 @@
#!/usr/bin/env python3
"""
Integration tests for Mem0 Interface - Zero Mocking, Real API calls
Tests against running Docker Compose stack (PostgreSQL + Neo4j + FastAPI)
Usage:
python test_integration.py # Run all tests (quiet)
python test_integration.py -v # Run with verbose output
python test_integration.py --help # Show help
"""
import requests
import json
import sys
import argparse
from datetime import datetime
import time
BASE_URL = "http://localhost:8000"
TEST_USER = f"test_user_{int(datetime.now().timestamp())}"
def main():
parser = argparse.ArgumentParser(
description="Mem0 Integration Tests - Real API Testing (Zero Mocking)",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("--verbose", "-v", action="store_true",
help="Show detailed output and API responses")
args = parser.parse_args()
verbose = args.verbose
print("🧪 Mem0 Integration Tests - Real API Testing")
print(f"🎯 Target: {BASE_URL}")
print(f"👤 Test User: {TEST_USER}")
print(f"⏰ Started: {datetime.now().strftime('%H:%M:%S')}")
print("=" * 50)
# Test sequence - order matters for data dependencies
tests = [
test_health_check,
test_empty_search_protection,
test_add_memories_with_hierarchy,
test_search_memories_basic,
test_search_memories_hierarchy_filters,
test_get_user_memories_with_hierarchy,
test_memory_history,
test_update_memory,
test_chat_with_memory,
test_graph_relationships_creation,
test_graph_relationships,
test_delete_specific_memory,
test_delete_all_user_memories,
test_cleanup_verification
]
results = []
start_time = time.time()
for test in tests:
result = run_test(test.__name__, test, verbose)
results.append(result)
# Small delay between tests for API stability
time.sleep(0.5)
# Summary
end_time = time.time()
duration = end_time - start_time
passed = sum(1 for r in results if r)
total = len(results)
print("=" * 50)
print(f"📊 Test Results: {passed}/{total} tests passed")
print(f"⏱️ Duration: {duration:.2f} seconds")
if passed == total:
print("✅ All tests passed! System is working correctly.")
sys.exit(0)
else:
print("❌ Some tests failed! Check the output above.")
sys.exit(1)
def run_test(name, test_func, verbose):
"""Run a single test with error handling"""
try:
if verbose:
print(f"\n🔍 Running {name}...")
test_func(verbose)
print(f"{name}")
return True
except AssertionError as e:
print(f"{name}: Assertion failed - {e}")
return False
except requests.exceptions.ConnectionError:
print(f"{name}: Cannot connect to {BASE_URL} - Is the server running?")
return False
except Exception as e:
print(f"{name}: {e}")
return False
def log_response(response, verbose, context=""):
"""Log API response details if verbose"""
if verbose:
print(f" {context} Status: {response.status_code}")
try:
data = response.json()
if isinstance(data, dict) and len(data) < 5:
print(f" {context} Response: {data}")
else:
print(f" {context} Response keys: {list(data.keys()) if isinstance(data, dict) else 'list'}")
except:
print(f" {context} Response: {response.text[:100]}...")
# ================== TEST FUNCTIONS ==================
def test_health_check(verbose):
"""Test service health endpoint"""
response = requests.get(f"{BASE_URL}/health", timeout=10)
log_response(response, verbose, "Health")
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "status" in data, "Health response missing 'status' field"
assert data["status"] in ["healthy", "degraded"], f"Invalid status: {data['status']}"
# Check individual services
assert "services" in data, "Health response missing 'services' field"
if verbose:
print(f" Overall status: {data['status']}")
for service, status in data["services"].items():
print(f" {service}: {status}")
def test_empty_search_protection(verbose):
"""Test empty query protection (should not return 500 error)"""
payload = {
"query": "",
"user_id": TEST_USER,
"limit": 5
}
response = requests.post(f"{BASE_URL}/memories/search", json=payload, timeout=10)
log_response(response, verbose, "Empty Search")
assert response.status_code == 200, f"Empty query failed with {response.status_code}"
data = response.json()
assert data["memories"] == [], "Empty query should return empty memories list"
assert "note" in data, "Empty query response should include explanatory note"
assert data["query"] == "", "Query should be echoed back"
if verbose:
print(f" Empty search note: {data['note']}")
print(f" Total count: {data.get('total_count', 0)}")
def test_add_memories_with_hierarchy(verbose):
"""Test adding memories with multi-level hierarchy support"""
payload = {
"messages": [
{"role": "user", "content": "I work at TechCorp as a Senior Software Engineer"},
{"role": "user", "content": "My colleague Sarah from Marketing team helped with Q3 presentation"},
{"role": "user", "content": "Meeting with John the Product Manager tomorrow about new feature development"}
],
"user_id": TEST_USER,
"agent_id": "test_agent",
"run_id": "test_run_001",
"session_id": "test_session_001",
"metadata": {"test": "integration", "scenario": "work_context"}
}
response = requests.post(f"{BASE_URL}/memories", json=payload, timeout=60)
log_response(response, verbose, "Add Memories")
assert response.status_code == 200, f"Add memories failed with {response.status_code}"
data = response.json()
assert "added_memories" in data, "Response missing 'added_memories'"
assert "message" in data, "Response missing success message"
assert len(data["added_memories"]) > 0, "No memories were added"
# Verify graph extraction (if available)
memories = data["added_memories"]
if isinstance(memories, list) and len(memories) > 0:
first_memory = memories[0]
if "relations" in first_memory:
relations = first_memory["relations"]
if "added_entities" in relations and relations["added_entities"]:
if verbose:
print(f" Graph extracted: {len(relations['added_entities'])} relationships")
print(f" Sample relations: {relations['added_entities'][:3]}")
if verbose:
print(f" Added {len(memories)} memory blocks")
print(f" Hierarchy - Agent: test_agent, Run: test_run_001, Session: test_session_001")
def test_search_memories_basic(verbose):
"""Test basic memory search functionality"""
# Test meaningful search
payload = {
"query": "TechCorp",
"user_id": TEST_USER,
"limit": 10
}
response = requests.post(f"{BASE_URL}/memories/search", json=payload, timeout=15)
log_response(response, verbose, "Search")
assert response.status_code == 200, f"Search failed with {response.status_code}"
data = response.json()
assert "memories" in data, "Search response missing 'memories'"
assert "total_count" in data, "Search response missing 'total_count'"
assert "query" in data, "Search response missing 'query'"
assert data["query"] == "TechCorp", "Query not echoed correctly"
# Should find memories since we just added some
assert data["total_count"] > 0, "Search should find previously added memories"
assert len(data["memories"]) > 0, "Search should return memory results"
# Verify memory structure
memory = data["memories"][0]
assert "id" in memory, "Memory missing 'id'"
assert "memory" in memory, "Memory missing 'memory' content"
assert "user_id" in memory, "Memory missing 'user_id'"
if verbose:
print(f" Found {data['total_count']} memories")
print(f" First memory: {memory['memory'][:50]}...")
def test_search_memories_hierarchy_filters(verbose):
"""Test multi-level hierarchy filtering in search"""
# Test with hierarchy filters
payload = {
"query": "TechCorp",
"user_id": TEST_USER,
"agent_id": "test_agent",
"run_id": "test_run_001",
"session_id": "test_session_001",
"limit": 10
}
response = requests.post(f"{BASE_URL}/memories/search", json=payload, timeout=15)
log_response(response, verbose, "Hierarchy Search")
assert response.status_code == 200, f"Hierarchy search failed with {response.status_code}"
data = response.json()
assert "memories" in data, "Hierarchy search response missing 'memories'"
# Should find memories since we added with these exact hierarchy values
assert len(data["memories"]) > 0, "Should find memories with matching hierarchy"
if verbose:
print(f" Found {len(data['memories'])} memories with hierarchy filters")
print(f" Filters: agent_id=test_agent, run_id=test_run_001, session_id=test_session_001")
def test_get_user_memories_with_hierarchy(verbose):
"""Test retrieving user memories with hierarchy filtering"""
# Test with hierarchy parameters
params = {
"limit": 20,
"agent_id": "test_agent",
"run_id": "test_run_001",
"session_id": "test_session_001"
}
response = requests.get(f"{BASE_URL}/memories/{TEST_USER}", params=params, timeout=15)
log_response(response, verbose, "Get User Memories with Hierarchy")
assert response.status_code == 200, f"Get user memories with hierarchy failed with {response.status_code}"
memories = response.json()
assert isinstance(memories, list), "User memories should return a list"
if len(memories) > 0:
memory = memories[0]
assert "id" in memory, "Memory missing 'id'"
assert "memory" in memory, "Memory missing 'memory' content"
assert memory["user_id"] == TEST_USER, f"Wrong user_id: {memory['user_id']}"
if verbose:
print(f" Retrieved {len(memories)} memories with hierarchy filters")
print(f" First memory: {memory['memory'][:40]}...")
else:
if verbose:
print(" No memories found with hierarchy filters (may be expected)")
def test_memory_history(verbose):
"""Test memory history endpoint"""
# First get a memory to check history for
response = requests.get(f"{BASE_URL}/memories/{TEST_USER}?limit=1", timeout=10)
assert response.status_code == 200, "Failed to get memory for history test"
memories = response.json()
if len(memories) == 0:
if verbose:
print(" No memories available for history test (skipping)")
return
memory_id = memories[0]["id"]
# Test memory history endpoint
response = requests.get(f"{BASE_URL}/memories/{memory_id}/history", timeout=15)
log_response(response, verbose, "Memory History")
assert response.status_code == 200, f"Memory history failed with {response.status_code}"
data = response.json()
assert "memory_id" in data, "History response missing 'memory_id'"
assert "history" in data, "History response missing 'history'"
assert "message" in data, "History response missing success message"
assert data["memory_id"] == memory_id, f"Wrong memory_id in response: {data['memory_id']}"
if verbose:
print(f" Retrieved history for memory {memory_id}")
print(f" History entries: {len(data['history']) if isinstance(data['history'], list) else 'N/A'}")
def test_update_memory(verbose):
"""Test updating a specific memory"""
# First get a memory to update
response = requests.get(f"{BASE_URL}/memories/{TEST_USER}?limit=1", timeout=10)
assert response.status_code == 200, "Failed to get memory for update test"
memories = response.json()
assert len(memories) > 0, "No memories available to update"
memory_id = memories[0]["id"]
original_content = memories[0]["memory"]
# Update the memory
payload = {
"memory_id": memory_id,
"content": f"UPDATED: {original_content}"
}
response = requests.put(f"{BASE_URL}/memories", json=payload, timeout=10)
log_response(response, verbose, "Update")
assert response.status_code == 200, f"Update failed with {response.status_code}"
data = response.json()
assert "message" in data, "Update response missing success message"
if verbose:
print(f" Updated memory {memory_id}")
print(f" Original: {original_content[:30]}...")
def test_chat_with_memory(verbose):
"""Test memory-enhanced chat functionality"""
payload = {
"message": "What company do I work for?",
"user_id": TEST_USER
}
try:
response = requests.post(f"{BASE_URL}/chat", json=payload, timeout=90)
log_response(response, verbose, "Chat")
assert response.status_code == 200, f"Chat failed with {response.status_code}"
data = response.json()
assert "response" in data, "Chat response missing 'response'"
assert "memories_used" in data, "Chat response missing 'memories_used'"
assert "model_used" in data, "Chat response missing 'model_used'"
# Should use some memories for context
assert data["memories_used"] >= 0, "Memories used should be non-negative"
if verbose:
print(f" Chat response: {data['response'][:60]}...")
print(f" Memories used: {data['memories_used']}")
print(f" Model: {data['model_used']}")
except requests.exceptions.ReadTimeout:
if verbose:
print(" Chat endpoint timed out (LLM API may be slow)")
# Still test that the endpoint exists and accepts requests
try:
response = requests.post(f"{BASE_URL}/chat", json=payload, timeout=5)
except requests.exceptions.ReadTimeout:
# This is expected - endpoint exists but processing is slow
if verbose:
print(" Chat endpoint confirmed active (processing timeout expected)")
def test_graph_relationships_creation(verbose):
"""Test graph relationships creation with entity-rich memories"""
# Create a separate test user for graph relationship testing
graph_test_user = f"graph_test_user_{int(datetime.now().timestamp())}"
# Add memories with clear entity relationships
payload = {
"messages": [
{"role": "user", "content": "John Smith works at Microsoft as a Senior Software Engineer"},
{"role": "user", "content": "John Smith is friends with Sarah Johnson who works at Google"},
{"role": "user", "content": "Sarah Johnson lives in Seattle and loves hiking"},
{"role": "user", "content": "Microsoft is located in Redmond, Washington"},
{"role": "user", "content": "John Smith and Sarah Johnson both graduated from Stanford University"}
],
"user_id": graph_test_user,
"metadata": {"test": "graph_relationships", "scenario": "entity_creation"}
}
response = requests.post(f"{BASE_URL}/memories", json=payload, timeout=60)
log_response(response, verbose, "Add Graph Memories")
assert response.status_code == 200, f"Add graph memories failed with {response.status_code}"
data = response.json()
assert "added_memories" in data, "Response missing 'added_memories'"
if verbose:
print(f" Added {len(data['added_memories'])} memories for graph relationship testing")
# Wait a moment for graph processing (Mem0 graph extraction can be async)
time.sleep(2)
# Test graph relationships endpoint
response = requests.get(f"{BASE_URL}/graph/relationships/{graph_test_user}", timeout=15)
log_response(response, verbose, "Graph Relationships")
assert response.status_code == 200, f"Graph relationships failed with {response.status_code}"
graph_data = response.json()
assert "relationships" in graph_data, "Graph response missing 'relationships'"
assert "entities" in graph_data, "Graph response missing 'entities'"
assert "user_id" in graph_data, "Graph response missing 'user_id'"
assert graph_data["user_id"] == graph_test_user, f"Wrong user_id in graph: {graph_data['user_id']}"
relationships = graph_data["relationships"]
entities = graph_data["entities"]
if verbose:
print(f" Found {len(relationships)} relationships")
print(f" Found {len(entities)} entities")
# Print sample relationships if they exist
if relationships:
print(f" Sample relationships:")
for i, rel in enumerate(relationships[:3]): # Show first 3
source = rel.get("source", "unknown")
target = rel.get("target", "unknown")
relationship = rel.get("relationship", "unknown")
print(f" {i+1}. {source} --{relationship}--> {target}")
# Print sample entities if they exist
if entities:
print(f" Sample entities: {[e.get('name', str(e)) for e in entities[:5]]}")
# Verify relationship structure (if relationships exist)
for rel in relationships:
assert "source" in rel or "from" in rel, f"Relationship missing source/from: {rel}"
assert "target" in rel or "to" in rel, f"Relationship missing target/to: {rel}"
assert "relationship" in rel or "type" in rel, f"Relationship missing type: {rel}"
# Clean up graph test user memories
cleanup_response = requests.delete(f"{BASE_URL}/memories/user/{graph_test_user}", timeout=15)
assert cleanup_response.status_code == 200, "Failed to cleanup graph test memories"
if verbose:
print(f" Cleaned up graph test user: {graph_test_user}")
# Note: We expect some relationships even if graph extraction is basic
# The test passes if the endpoint works and returns proper structure
def test_graph_relationships(verbose):
"""Test graph relationships endpoint"""
response = requests.get(f"{BASE_URL}/graph/relationships/{TEST_USER}", timeout=15)
log_response(response, verbose, "Graph")
assert response.status_code == 200, f"Graph endpoint failed with {response.status_code}"
data = response.json()
assert "relationships" in data, "Graph response missing 'relationships'"
assert "entities" in data, "Graph response missing 'entities'"
assert "user_id" in data, "Graph response missing 'user_id'"
assert data["user_id"] == TEST_USER, f"Wrong user_id in graph: {data['user_id']}"
if verbose:
print(f" Relationships: {len(data['relationships'])}")
print(f" Entities: {len(data['entities'])}")
def test_delete_specific_memory(verbose):
"""Test deleting a specific memory"""
# Get a memory to delete
response = requests.get(f"{BASE_URL}/memories/{TEST_USER}?limit=1", timeout=10)
assert response.status_code == 200, "Failed to get memory for deletion test"
memories = response.json()
assert len(memories) > 0, "No memories available to delete"
memory_id = memories[0]["id"]
# Delete the memory
response = requests.delete(f"{BASE_URL}/memories/{memory_id}", timeout=10)
log_response(response, verbose, "Delete")
assert response.status_code == 200, f"Delete failed with {response.status_code}"
data = response.json()
assert "message" in data, "Delete response missing success message"
if verbose:
print(f" Deleted memory {memory_id}")
def test_delete_all_user_memories(verbose):
"""Test deleting all memories for a user"""
response = requests.delete(f"{BASE_URL}/memories/user/{TEST_USER}", timeout=15)
log_response(response, verbose, "Delete All")
assert response.status_code == 200, f"Delete all failed with {response.status_code}"
data = response.json()
assert "message" in data, "Delete all response missing success message"
if verbose:
print(f" Deleted all memories for {TEST_USER}")
def test_cleanup_verification(verbose):
"""Verify cleanup was successful"""
response = requests.get(f"{BASE_URL}/memories/{TEST_USER}?limit=10", timeout=10)
log_response(response, verbose, "Cleanup Check")
assert response.status_code == 200, f"Cleanup verification failed with {response.status_code}"
memories = response.json()
assert isinstance(memories, list), "Should return list even if empty"
# Should be empty after deletion
if len(memories) > 0:
print(f" Warning: {len(memories)} memories still exist after cleanup")
else:
if verbose:
print(" Cleanup successful - no memories remain")
if __name__ == "__main__":
main()