mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
feat: Add memory vs rag and migration section to docs
This commit is contained in:
parent
53bc296155
commit
5c57578573
5 changed files with 985 additions and 50 deletions
|
|
@ -71,7 +71,11 @@
|
|||
"pages": [
|
||||
{
|
||||
"group": "Getting Started",
|
||||
"pages": ["intro", "routervsapi", "quickstart"]
|
||||
"pages": ["intro", "routervsapi", "quickstart", "memory-vs-rag"]
|
||||
},
|
||||
{
|
||||
"group": "Migration Guides",
|
||||
"pages": ["migration/from-mem0"]
|
||||
},
|
||||
{
|
||||
"group": "Memory API",
|
||||
|
|
@ -83,7 +87,7 @@
|
|||
"pages": [
|
||||
"add-memories/overview",
|
||||
"add-memories/parameters",
|
||||
"memory-api/ingesting",
|
||||
"api/ingesting",
|
||||
{
|
||||
"group": "Examples",
|
||||
"pages": [
|
||||
|
|
@ -112,7 +116,7 @@
|
|||
]
|
||||
},
|
||||
"search/filtering",
|
||||
"memory-api/track-progress",
|
||||
"api/track-progress",
|
||||
{
|
||||
"group": "List Memories",
|
||||
"icon": "list",
|
||||
|
|
@ -184,11 +188,6 @@
|
|||
"icon": "sparkles",
|
||||
"pages": ["memory-api/sdks/openai-plugins", "ai-sdk/npm"]
|
||||
},
|
||||
{
|
||||
"group": "Anthropic SDK",
|
||||
"icon": "sparkles",
|
||||
"pages": ["memory-api/sdks/anthropic-claude-memory"]
|
||||
},
|
||||
{
|
||||
"group": "AI SDK",
|
||||
"icon": "triangle",
|
||||
|
|
|
|||
239
apps/docs/memory-vs-rag.mdx
Normal file
239
apps/docs/memory-vs-rag.mdx
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
---
|
||||
title: "Memory vs RAG: Understanding the Difference"
|
||||
description: "Learn why agent memory and RAG are fundamentally different, and when to use each approach"
|
||||
sidebarTitle: "Memory vs RAG"
|
||||
---
|
||||
|
||||
Most developers confuse RAG (Retrieval-Augmented Generation) with agent memory. They're not the same thing, and using RAG for memory is why your agents keep forgetting important context. Let's understand the fundamental difference.
|
||||
|
||||
## The Core Problem
|
||||
|
||||
When building AI agents, developers often treat memory as just another retrieval problem. They store conversations in a vector database, embed queries, and hope semantic search will surface the right context.
|
||||
|
||||
**This approach fails because memory isn't about finding similar text—it's about understanding relationships, temporal context, and user state over time.**
|
||||
|
||||
## Documents vs Memories in Supermemory
|
||||
|
||||
Supermemory makes a clear distinction between these two concepts:
|
||||
|
||||
### Documents: Raw Knowledge
|
||||
Documents are the raw content you send to Supermemory—PDFs, web pages, text files. They represent static knowledge that doesn't change based on who's accessing it.
|
||||
|
||||
**Characteristics:**
|
||||
- **Stateless**: A document about Python programming is the same for everyone
|
||||
- **Unversioned**: Content doesn't track changes over time
|
||||
- **Universal**: Not linked to specific users or entities
|
||||
- **Searchable**: Perfect for semantic similarity search
|
||||
|
||||
**Use Cases:**
|
||||
- Company knowledge bases
|
||||
- Technical documentation
|
||||
- Research papers
|
||||
- General reference material
|
||||
|
||||
### Memories: Contextual Understanding
|
||||
Memories are the insights, preferences, and relationships extracted from documents and conversations. They're tied to specific users or entities and evolve over time.
|
||||
|
||||
**Characteristics:**
|
||||
- **Stateful**: "User prefers dark mode" is specific to that user
|
||||
- **Temporal**: Tracks when facts became true or invalid
|
||||
- **Personal**: Linked to users, sessions, or entities
|
||||
- **Relational**: Understands connections between facts
|
||||
|
||||
**Use Cases:**
|
||||
- User preferences and history
|
||||
- Conversation context
|
||||
- Personal facts and relationships
|
||||
- Behavioral patterns
|
||||
|
||||
## Why RAG Fails as Memory
|
||||
|
||||
Let's look at a real scenario that illustrates the problem:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="The Scenario">
|
||||
```
|
||||
Day 1: "I love Adidas sneakers"
|
||||
Day 30: "My Adidas broke after a month, terrible quality"
|
||||
Day 31: "I'm switching to Puma"
|
||||
Day 45: "What sneakers should I buy?"
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="RAG Approach (Wrong)">
|
||||
```python
|
||||
# RAG sees these as isolated embeddings
|
||||
query = "What sneakers should I buy?"
|
||||
|
||||
# Semantic search finds closest match
|
||||
result = vector_search(query)
|
||||
# Returns: "I love Adidas sneakers" (highest similarity)
|
||||
|
||||
# Agent recommends Adidas 🤦
|
||||
```
|
||||
|
||||
**Problem**: RAG finds the most semantically similar text but misses the temporal progression and causal relationships.
|
||||
</Tab>
|
||||
|
||||
<Tab title="Memory Approach (Right)">
|
||||
```python
|
||||
# Supermemory understands temporal context
|
||||
query = "What sneakers should I buy?"
|
||||
|
||||
# Memory retrieval considers:
|
||||
# 1. Temporal validity (Adidas preference is outdated)
|
||||
# 2. Causal relationships (broke → disappointment → switch)
|
||||
# 3. Current state (now prefers Puma)
|
||||
|
||||
# Agent correctly recommends Puma ✅
|
||||
```
|
||||
|
||||
**Solution**: Memory systems track when facts become invalid and understand causal chains.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## The Technical Difference
|
||||
|
||||
### RAG: Semantic Similarity
|
||||
```
|
||||
Query → Embedding → Vector Search → Top-K Results → LLM
|
||||
```
|
||||
|
||||
RAG excels at finding information that's semantically similar to your query. It's stateless—each query is independent.
|
||||
|
||||
### Memory: Contextual Graph
|
||||
```
|
||||
Query → Entity Recognition → Graph Traversal → Temporal Filtering → Context Assembly → LLM
|
||||
```
|
||||
|
||||
Memory systems build a knowledge graph that understands:
|
||||
- **Entities**: Users, products, concepts
|
||||
- **Relationships**: Preferences, ownership, causality
|
||||
- **Temporal Context**: When facts were true
|
||||
- **Invalidation**: When facts became outdated
|
||||
|
||||
## When to Use Each
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Use RAG For" icon="search">
|
||||
- Static documentation
|
||||
- Knowledge bases
|
||||
- Research queries
|
||||
- General Q&A
|
||||
- Content that doesn't change per user
|
||||
</Card>
|
||||
|
||||
<Card title="Use Memory For" icon="brain">
|
||||
- User preferences
|
||||
- Conversation history
|
||||
- Personal facts
|
||||
- Behavioral patterns
|
||||
- Anything that evolves over time
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Real-World Examples
|
||||
|
||||
### E-commerce Assistant
|
||||
|
||||
<Tabs>
|
||||
<Tab title="RAG Component">
|
||||
Stores product catalogs, specifications, reviews
|
||||
|
||||
```python
|
||||
# Good for RAG
|
||||
"What are the specs of iPhone 15?"
|
||||
"Compare Nike and Adidas running shoes"
|
||||
"Show me waterproof jackets"
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Memory Component">
|
||||
Tracks user preferences, purchase history, interactions
|
||||
|
||||
```python
|
||||
# Needs Memory
|
||||
"What size do I usually wear?"
|
||||
"Did I like my last purchase?"
|
||||
"What's my budget preference?"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Customer Support Bot
|
||||
|
||||
<Tabs>
|
||||
<Tab title="RAG Component">
|
||||
FAQ documents, troubleshooting guides, policies
|
||||
|
||||
```python
|
||||
# Good for RAG
|
||||
"How do I reset my password?"
|
||||
"What's your return policy?"
|
||||
"Troubleshooting WiFi issues"
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Memory Component">
|
||||
Previous issues, user account details, conversation context
|
||||
|
||||
```python
|
||||
# Needs Memory
|
||||
"Is my issue from last week resolved?"
|
||||
"What plan am I on?"
|
||||
"You were helping me with..."
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## How Supermemory Handles Both
|
||||
|
||||
Supermemory provides a unified platform that correctly handles both patterns:
|
||||
|
||||
### 1. Document Storage (RAG)
|
||||
```python
|
||||
# Add a document for RAG-style retrieval
|
||||
client.memories.add(
|
||||
content="iPhone 15 has a 48MP camera and A17 Pro chip",
|
||||
# No user association - universal knowledge
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Memory Creation
|
||||
```python
|
||||
# Add a user-specific memory
|
||||
client.memories.add(
|
||||
content="User prefers Android over iOS",
|
||||
container_tags=["user_123"], # User-specific
|
||||
metadata={
|
||||
"type": "preference",
|
||||
"confidence": "high"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### 3. Hybrid Retrieval
|
||||
```python
|
||||
# Search combines both approaches
|
||||
results = client.memories.search(
|
||||
query="What phone should I recommend?",
|
||||
container_tags=["user_123"], # Gets user memories
|
||||
# Also searches general knowledge
|
||||
)
|
||||
|
||||
# Results include:
|
||||
# - User's Android preference (memory)
|
||||
# - Latest Android phone specs (documents)
|
||||
```
|
||||
|
||||
## The Bottom Line
|
||||
|
||||
<Note>
|
||||
**Key Insight**: RAG answers "What do I know?" while Memory answers "What do I remember about you?"
|
||||
</Note>
|
||||
|
||||
Stop treating memory like a retrieval problem. Your agents need both:
|
||||
- **RAG** for accessing knowledge
|
||||
- **Memory** for understanding users
|
||||
|
||||
Supermemory provides both capabilities in a unified platform, ensuring your agents have the right context at the right time.
|
||||
303
apps/docs/migration/from-mem0.mdx
Normal file
303
apps/docs/migration/from-mem0.mdx
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
---
|
||||
title: "Migrating from Mem0.ai to Supermemory"
|
||||
description: "Complete guide to migrate your data and applications from Mem0.ai to Supermemory"
|
||||
sidebarTitle: "From Mem0"
|
||||
---
|
||||
|
||||
Migrating from Mem0.ai to Supermemory is straightforward. This guide walks you through exporting your memories from Mem0 and importing them into Supermemory.
|
||||
|
||||
## Why Migrate to Supermemory?
|
||||
|
||||
Supermemory offers enhanced capabilities over Mem0.ai:
|
||||
- **Memory Router** for zero-code LLM integration
|
||||
- **Knowledge graph** architecture for better context relationships
|
||||
- **Multiple content types** (URLs, PDFs, images, videos)
|
||||
- **Generous free tier** (100k tokens) with affordable pricing
|
||||
- **Multiple integration options** (API, Router, MCP, SDKs)
|
||||
|
||||
## Quick Migration (All-in-One)
|
||||
|
||||
Complete migration in one script:
|
||||
|
||||
```python
|
||||
from mem0 import MemoryClient
|
||||
from supermemory import Supermemory
|
||||
import json, time
|
||||
|
||||
# Export from Mem0
|
||||
mem0 = MemoryClient(api_key="your_mem0_api_key")
|
||||
export = mem0.create_memory_export(
|
||||
schema={"type": "object", "properties": {"memories": {"type": "array", "items": {"type": "object"}}}},
|
||||
filters={}
|
||||
)
|
||||
time.sleep(5)
|
||||
data = mem0.get_memory_export(memory_export_id=export["id"])
|
||||
|
||||
# Import to Supermemory
|
||||
supermemory = Supermemory(api_key="your_supermemory_api_key")
|
||||
for memory in data["memories"]:
|
||||
if memory.get("content"):
|
||||
supermemory.memories.add(
|
||||
content=memory["content"],
|
||||
container_tags=["imported_from_mem0"]
|
||||
)
|
||||
print(f"✅ {memory['content'][:50]}...")
|
||||
|
||||
print("Migration complete!")
|
||||
```
|
||||
|
||||
## Step-by-Step Migration
|
||||
|
||||
<Steps>
|
||||
<Step title="Export from Mem0.ai">
|
||||
Mem0 provides two ways to export your memories:
|
||||
|
||||
### Option 1: Export via Dashboard (Recommended)
|
||||
1. Log into your [Mem0 dashboard](https://app.mem0.ai)
|
||||
2. Navigate to the export section
|
||||
3. Download your memories as JSON
|
||||
|
||||
### Option 2: Export via API
|
||||
|
||||
Simple script to export all your memories from Mem0:
|
||||
|
||||
```python
|
||||
from mem0 import MemoryClient
|
||||
import json
|
||||
import time
|
||||
|
||||
# Connect to Mem0
|
||||
client = MemoryClient(api_key="your_mem0_api_key")
|
||||
|
||||
# Create export job
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"memories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string"},
|
||||
"content": {"type": "string"},
|
||||
"metadata": {"type": "object"},
|
||||
"created_at": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response = client.create_memory_export(schema=schema, filters={})
|
||||
export_id = response["id"]
|
||||
|
||||
# Wait and retrieve
|
||||
print("Exporting memories...")
|
||||
time.sleep(5)
|
||||
export_data = client.get_memory_export(memory_export_id=export_id)
|
||||
|
||||
# Save to file
|
||||
with open("mem0_export.json", "w") as f:
|
||||
json.dump(export_data, f, indent=2)
|
||||
|
||||
print(f"Exported {len(export_data['memories'])} memories")
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Set Up Supermemory">
|
||||
Create your Supermemory account and get your API key:
|
||||
|
||||
1. Sign up at [console.supermemory.ai](https://console.supermemory.ai)
|
||||
2. Create a new project
|
||||
3. Generate an API key from the dashboard
|
||||
|
||||
```bash
|
||||
# Set your environment variable
|
||||
export SUPERMEMORY_API_KEY="your_supermemory_api_key"
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Import to Supermemory">
|
||||
Simple script to import your Mem0 memories into Supermemory:
|
||||
|
||||
```python
|
||||
import json
|
||||
from supermemory import Supermemory
|
||||
|
||||
# Load your Mem0 export
|
||||
with open("mem0_export.json", "r") as f:
|
||||
mem0_data = json.load(f)
|
||||
|
||||
# Connect to Supermemory
|
||||
client = Supermemory(api_key="your_supermemory_api_key")
|
||||
|
||||
# Import memories
|
||||
for memory in mem0_data["memories"]:
|
||||
content = memory.get("content", "")
|
||||
|
||||
# Skip empty memories
|
||||
if not content:
|
||||
continue
|
||||
|
||||
# Import to Supermemory
|
||||
try:
|
||||
result = client.memories.add(
|
||||
content=content,
|
||||
container_tags=["imported_from_mem0"],
|
||||
metadata={
|
||||
"source": "mem0",
|
||||
"created_at": memory.get("created_at"),
|
||||
**(memory.get("metadata") or {})
|
||||
}
|
||||
)
|
||||
print(f"Imported: {content[:50]}...")
|
||||
except Exception as e:
|
||||
print(f"Failed: {e}")
|
||||
|
||||
print("Migration complete!")
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## API Migration Reference
|
||||
|
||||
Here's how common Mem0.ai operations map to Supermemory:
|
||||
|
||||
### Adding Memories
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Mem0.ai
|
||||
from mem0 import MemoryClient
|
||||
|
||||
client = MemoryClient(api_key="...")
|
||||
client.add(
|
||||
messages="User prefers dark mode",
|
||||
user_id="alice"
|
||||
)
|
||||
```
|
||||
|
||||
```python Supermemory
|
||||
from supermemory import Supermemory
|
||||
|
||||
client = Supermemory(api_key="...")
|
||||
client.memories.add(
|
||||
content="User prefers dark mode",
|
||||
container_tags=["user_alice"]
|
||||
)
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Searching Memories
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Mem0.ai
|
||||
results = client.search(
|
||||
query="user preferences",
|
||||
user_id="alice"
|
||||
)
|
||||
```
|
||||
|
||||
```python Supermemory
|
||||
results = client.memories.search(
|
||||
query="user preferences",
|
||||
container_tags=["user_alice"]
|
||||
)
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Getting All Memories
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Mem0.ai
|
||||
memories = client.get_all(
|
||||
user_id="alice"
|
||||
)
|
||||
```
|
||||
|
||||
```python Supermemory
|
||||
memories = client.memories.list(
|
||||
container_tags=["user_alice"],
|
||||
limit=100
|
||||
)
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### Deleting Memories
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Mem0.ai
|
||||
client.delete(memory_id="mem_123")
|
||||
```
|
||||
|
||||
```python Supermemory
|
||||
client.memories.delete("mem_123")
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Using Memory Router (Easiest Migration)
|
||||
|
||||
For the simplest migration path, use Supermemory's Memory Router which requires minimal code changes:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python Before (Mem0 + OpenAI)
|
||||
from openai import OpenAI
|
||||
from mem0 import MemoryClient
|
||||
|
||||
# Two separate clients needed
|
||||
openai = OpenAI(api_key="sk-...")
|
||||
memory = MemoryClient(api_key="mem0_key")
|
||||
|
||||
# Manual memory management
|
||||
context = memory.search("user preferences", user_id="alice")
|
||||
messages = [
|
||||
{"role": "system", "content": f"Context: {context}"},
|
||||
{"role": "user", "content": "What are my preferences?"}
|
||||
]
|
||||
|
||||
response = openai.chat.completions.create(
|
||||
model="gpt-4",
|
||||
messages=messages
|
||||
)
|
||||
```
|
||||
|
||||
```python After (Supermemory Router)
|
||||
from openai import OpenAI
|
||||
|
||||
# Single client with automatic memory management
|
||||
client = OpenAI(
|
||||
api_key="sk-...",
|
||||
base_url="https://api.supermemory.ai/v3/https://api.openai.com/v1",
|
||||
default_headers={
|
||||
"x-supermemory-api-key": "your_supermemory_key",
|
||||
"x-supermemory-user-id": "alice"
|
||||
}
|
||||
)
|
||||
|
||||
# Memories handled automatically!
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-4",
|
||||
messages=[{"role": "user", "content": "What are my preferences?"}]
|
||||
)
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
<Note>
|
||||
For enterprise migrations, [contact us](mailto:dhravya@supermemory.com) for assistance.
|
||||
</Note>
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. [Explore](/how-it-works) how Supermemory works
|
||||
2. Read the [quickstart](/quickstart) and add and retrieve your first memories
|
||||
3. [Connect](/connectors/overview) to Google Drive, Notion, and OneDrive with automatic syncing
|
||||
|
||||
337
apps/docs/migration/mem0-migration-script.py
Normal file
337
apps/docs/migration/mem0-migration-script.py
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Mem0.ai to Supermemory Migration Script
|
||||
========================================
|
||||
Simple script to migrate memories from Mem0.ai to Supermemory.
|
||||
|
||||
Prerequisites:
|
||||
1. Install required packages:
|
||||
pip install mem0ai supermemory python-dotenv
|
||||
|
||||
2. Set environment variables:
|
||||
export MEM0_API_KEY="your_mem0_api_key"
|
||||
export MEM0_ORG_ID="your_org_id" # Optional
|
||||
export MEM0_PROJECT_ID="your_project_id" # Optional
|
||||
export SUPERMEMORY_API_KEY="your_supermemory_api_key"
|
||||
|
||||
Usage:
|
||||
python mem0-migration-script.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional
|
||||
from mem0 import MemoryClient
|
||||
from supermemory import Supermemory
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
def export_from_mem0(
|
||||
api_key: str,
|
||||
org_id: Optional[str] = None,
|
||||
project_id: Optional[str] = None,
|
||||
filters: Optional[Dict] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Export memories from Mem0.ai using their export API
|
||||
"""
|
||||
print("🔄 Starting Mem0.ai export...")
|
||||
|
||||
# Initialize Mem0 client
|
||||
client = MemoryClient(
|
||||
api_key=api_key,
|
||||
org_id=org_id,
|
||||
project_id=project_id
|
||||
)
|
||||
|
||||
# Define export schema - this matches what Mem0 actually returns
|
||||
export_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"memories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string"},
|
||||
"content": {"type": "string"},
|
||||
"user_id": {"type": "string"},
|
||||
"agent_id": {"type": "string"},
|
||||
"app_id": {"type": "string"},
|
||||
"run_id": {"type": "string"},
|
||||
"metadata": {"type": "object"},
|
||||
"created_at": {"type": "string"},
|
||||
"updated_at": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
# Step 1: Create export job
|
||||
print("📤 Creating export job...")
|
||||
export_response = client.create_memory_export(
|
||||
schema=export_schema,
|
||||
filters=filters if filters else {}
|
||||
)
|
||||
|
||||
export_id = export_response.get("id")
|
||||
print(f"✅ Export job created with ID: {export_id}")
|
||||
|
||||
# Step 2: Wait for export to complete
|
||||
print("⏳ Waiting for export to complete...")
|
||||
time.sleep(5) # Usually takes a few seconds
|
||||
|
||||
# Step 3: Retrieve the exported data using the correct method
|
||||
print("📥 Retrieving exported data...")
|
||||
export_data = client.get_memory_export(memory_export_id=export_id)
|
||||
|
||||
# Step 4: Save backup
|
||||
backup_filename = f"mem0_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
||||
with open(backup_filename, "w") as f:
|
||||
json.dump(export_data, f, indent=2)
|
||||
print(f"💾 Backup saved to: {backup_filename}")
|
||||
|
||||
memory_count = len(export_data.get("memories", []))
|
||||
print(f"✅ Successfully exported {memory_count} memories from Mem0.ai")
|
||||
|
||||
# Show sample of exported data
|
||||
if memory_count > 0:
|
||||
print("\n📋 Sample exported memory:")
|
||||
sample = export_data["memories"][0]
|
||||
print(f" Content: {sample.get('content', 'N/A')[:50]}...")
|
||||
print(f" ID: {sample.get('id', 'None')}")
|
||||
print(f" User ID: {sample.get('user_id', 'None')}")
|
||||
|
||||
return export_data
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error exporting from Mem0: {str(e)}")
|
||||
raise
|
||||
|
||||
def import_to_supermemory(mem0_data: Dict[str, Any], api_key: str) -> Dict[str, int]:
|
||||
"""
|
||||
Import Mem0 memories into Supermemory
|
||||
"""
|
||||
print("\n🚀 Starting import to Supermemory...")
|
||||
|
||||
# Initialize Supermemory client
|
||||
client = Supermemory(api_key=api_key)
|
||||
|
||||
memories = mem0_data.get("memories", [])
|
||||
if not memories:
|
||||
print("⚠️ No memories found to import")
|
||||
return {"imported": 0, "failed": 0, "skipped": 0}
|
||||
|
||||
# Statistics
|
||||
stats = {
|
||||
"imported": 0,
|
||||
"failed": 0,
|
||||
"skipped": 0
|
||||
}
|
||||
|
||||
print(f"📦 Processing {len(memories)} memories...")
|
||||
|
||||
for i, memory in enumerate(memories, 1):
|
||||
try:
|
||||
# Check if content exists
|
||||
content = memory.get("content", "").strip()
|
||||
if not content:
|
||||
print(f"⚠️ [{i}/{len(memories)}] Skipping: No content")
|
||||
stats["skipped"] += 1
|
||||
continue
|
||||
|
||||
# Build container tags
|
||||
container_tags = ["imported_from_mem0"]
|
||||
|
||||
# Add user tag if present (handle None values)
|
||||
user_id = memory.get("user_id")
|
||||
if user_id and user_id != "None":
|
||||
container_tags.append(f"user_{user_id}")
|
||||
|
||||
# Add agent tag if present
|
||||
agent_id = memory.get("agent_id")
|
||||
if agent_id and agent_id != "None":
|
||||
container_tags.append(f"agent_{agent_id}")
|
||||
|
||||
# Add app tag if present
|
||||
app_id = memory.get("app_id")
|
||||
if app_id and app_id != "None":
|
||||
container_tags.append(f"app_{app_id}")
|
||||
|
||||
# Add session tag if present
|
||||
session_id = memory.get("session_id")
|
||||
if session_id and session_id != "None":
|
||||
container_tags.append(f"session_{session_id}")
|
||||
|
||||
# Generate a unique ID if Mem0 didn't provide one
|
||||
memory_id = memory.get("id")
|
||||
if not memory_id or memory_id == "None":
|
||||
# Use content hash for uniqueness
|
||||
import hashlib
|
||||
memory_id = hashlib.md5(content.encode()).hexdigest()[:8]
|
||||
|
||||
# Prepare metadata
|
||||
metadata = {
|
||||
"source": "mem0_migration",
|
||||
"migration_date": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
# Add original ID if it existed
|
||||
if memory.get("id") and memory["id"] != "None":
|
||||
metadata["original_id"] = memory["id"]
|
||||
|
||||
# Add timestamps if available and not None
|
||||
created_at = memory.get("created_at")
|
||||
if created_at and created_at != "None":
|
||||
metadata["original_created_at"] = created_at
|
||||
|
||||
updated_at = memory.get("updated_at")
|
||||
if updated_at and updated_at != "None":
|
||||
metadata["original_updated_at"] = updated_at
|
||||
|
||||
# Add hash information if available
|
||||
hash_val = memory.get("hash")
|
||||
if hash_val and hash_val != "None":
|
||||
metadata["original_hash"] = hash_val
|
||||
|
||||
prev_hash = memory.get("prev_hash")
|
||||
if prev_hash and prev_hash != "None":
|
||||
metadata["original_prev_hash"] = prev_hash
|
||||
|
||||
# Merge with existing metadata if it's a valid dict
|
||||
if memory.get("metadata") and isinstance(memory["metadata"], dict):
|
||||
metadata.update(memory["metadata"])
|
||||
|
||||
# Import to Supermemory
|
||||
result = client.memories.add(
|
||||
content=content,
|
||||
container_tags=container_tags,
|
||||
custom_id=f"mem0_{memory_id}",
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
stats["imported"] += 1
|
||||
print(f"✅ [{i}/{len(memories)}] Imported: {content[:50]}...")
|
||||
|
||||
# Small delay to avoid rate limiting
|
||||
if i % 10 == 0:
|
||||
time.sleep(0.5)
|
||||
|
||||
except Exception as e:
|
||||
stats["failed"] += 1
|
||||
print(f"❌ [{i}/{len(memories)}] Failed: {str(e)}")
|
||||
|
||||
return stats
|
||||
|
||||
def verify_migration(api_key: str, expected_count: int):
|
||||
"""
|
||||
Verify that memories were imported correctly
|
||||
"""
|
||||
print("\n🔍 Verifying migration...")
|
||||
|
||||
client = Supermemory(api_key=api_key)
|
||||
|
||||
try:
|
||||
# Check imported memories
|
||||
result = client.memories.list(
|
||||
container_tags=["imported_from_mem0"],
|
||||
limit=100
|
||||
)
|
||||
|
||||
total_imported = result['pagination']['totalItems']
|
||||
print(f"✅ Found {total_imported} imported memories in Supermemory")
|
||||
|
||||
# Show sample memories
|
||||
if result['memories']:
|
||||
print("\n📋 Sample imported memories:")
|
||||
for memory in result['memories'][:3]:
|
||||
print(f" - {memory['id']}: {memory.get('summary', 'No summary')[:50]}...")
|
||||
|
||||
# Check success rate
|
||||
success_rate = (total_imported / expected_count * 100) if expected_count > 0 else 0
|
||||
print(f"\n📊 Migration success rate: {success_rate:.1f}%")
|
||||
|
||||
return total_imported
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error during verification: {str(e)}")
|
||||
return 0
|
||||
|
||||
def main():
|
||||
"""Main migration function"""
|
||||
print("=" * 60)
|
||||
print("🎯 Mem0.ai to Supermemory Migration Tool")
|
||||
print("=" * 60)
|
||||
|
||||
# Get credentials from environment
|
||||
mem0_api_key = os.getenv("MEM0_API_KEY")
|
||||
mem0_org_id = os.getenv("MEM0_ORG_ID")
|
||||
mem0_project_id = os.getenv("MEM0_PROJECT_ID")
|
||||
supermemory_api_key = os.getenv("SUPERMEMORY_API_KEY")
|
||||
|
||||
# Validate credentials
|
||||
if not mem0_api_key:
|
||||
print("❌ Error: MEM0_API_KEY environment variable not set")
|
||||
return
|
||||
|
||||
if not supermemory_api_key:
|
||||
print("❌ Error: SUPERMEMORY_API_KEY environment variable not set")
|
||||
return
|
||||
|
||||
try:
|
||||
# Step 1: Export from Mem0
|
||||
print("\n📤 STEP 1: Export from Mem0.ai")
|
||||
print("-" * 40)
|
||||
|
||||
# You can add filters here if needed
|
||||
# Example: filters = {"AND": [{"user_id": "specific_user"}]}
|
||||
filters = None
|
||||
|
||||
mem0_data = export_from_mem0(
|
||||
api_key=mem0_api_key,
|
||||
org_id=mem0_org_id,
|
||||
project_id=mem0_project_id,
|
||||
filters=filters
|
||||
)
|
||||
|
||||
# Step 2: Import to Supermemory
|
||||
print("\n📥 STEP 2: Import to Supermemory")
|
||||
print("-" * 40)
|
||||
|
||||
stats = import_to_supermemory(mem0_data, supermemory_api_key)
|
||||
|
||||
# Step 3: Verify migration
|
||||
print("\n✔️ STEP 3: Verification")
|
||||
print("-" * 40)
|
||||
|
||||
expected_count = len(mem0_data.get("memories", []))
|
||||
verify_migration(supermemory_api_key, expected_count)
|
||||
|
||||
# Final summary
|
||||
print("\n" + "=" * 60)
|
||||
print("📊 MIGRATION SUMMARY")
|
||||
print("=" * 60)
|
||||
print(f"📤 Exported from Mem0: {expected_count}")
|
||||
print(f"✅ Successfully imported: {stats['imported']}")
|
||||
print(f"⚠️ Skipped (no content): {stats['skipped']}")
|
||||
print(f"❌ Failed: {stats['failed']}")
|
||||
|
||||
if stats['imported'] == expected_count - stats['skipped']:
|
||||
print("\n🎉 Migration completed successfully!")
|
||||
elif stats['imported'] > 0:
|
||||
print("\n⚠️ Migration completed with some issues. Check the logs above.")
|
||||
else:
|
||||
print("\n❌ Migration failed. Please check your credentials and try again.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Migration error: {str(e)}")
|
||||
print("Please check your credentials and network connection.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import Supermemory from "supermemory"
|
||||
import type { SupermemoryToolsConfig } from "./types"
|
||||
import { getContainerTags } from "./shared"
|
||||
import type { SupermemoryToolsConfig } from "./types"
|
||||
|
||||
// Claude Memory Tool Types
|
||||
export interface ClaudeMemoryConfig extends SupermemoryToolsConfig {
|
||||
|
|
@ -92,24 +92,45 @@ export class ClaudeMemoryTool {
|
|||
return await this.view(command.path, command.view_range)
|
||||
case "create":
|
||||
if (!command.file_text) {
|
||||
return { success: false, error: "file_text is required for create command" }
|
||||
return {
|
||||
success: false,
|
||||
error: "file_text is required for create command",
|
||||
}
|
||||
}
|
||||
return await this.create(command.path, command.file_text)
|
||||
case "str_replace":
|
||||
if (!command.old_str || !command.new_str) {
|
||||
return { success: false, error: "old_str and new_str are required for str_replace command" }
|
||||
return {
|
||||
success: false,
|
||||
error: "old_str and new_str are required for str_replace command",
|
||||
}
|
||||
}
|
||||
return await this.strReplace(command.path, command.old_str, command.new_str)
|
||||
return await this.strReplace(
|
||||
command.path,
|
||||
command.old_str,
|
||||
command.new_str,
|
||||
)
|
||||
case "insert":
|
||||
if (command.insert_line === undefined || !command.insert_text) {
|
||||
return { success: false, error: "insert_line and insert_text are required for insert command" }
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"insert_line and insert_text are required for insert command",
|
||||
}
|
||||
}
|
||||
return await this.insert(command.path, command.insert_line, command.insert_text)
|
||||
return await this.insert(
|
||||
command.path,
|
||||
command.insert_line,
|
||||
command.insert_text,
|
||||
)
|
||||
case "delete":
|
||||
return await this.delete(command.path)
|
||||
case "rename":
|
||||
if (!command.new_path) {
|
||||
return { success: false, error: "new_path is required for rename command" }
|
||||
return {
|
||||
success: false,
|
||||
error: "new_path is required for rename command",
|
||||
}
|
||||
}
|
||||
return await this.rename(command.path, command.new_path)
|
||||
default:
|
||||
|
|
@ -129,14 +150,17 @@ export class ClaudeMemoryTool {
|
|||
/**
|
||||
* Handle command and return properly formatted tool result
|
||||
*/
|
||||
async handleCommandForToolResult(command: MemoryCommand, toolUseId: string): Promise<MemoryToolResult> {
|
||||
async handleCommandForToolResult(
|
||||
command: MemoryCommand,
|
||||
toolUseId: string,
|
||||
): Promise<MemoryToolResult> {
|
||||
const response = await this.handleCommand(command)
|
||||
|
||||
return {
|
||||
type: "tool_result",
|
||||
tool_use_id: toolUseId,
|
||||
content: response.success
|
||||
? (response.content || "Operation completed successfully")
|
||||
? response.content || "Operation completed successfully"
|
||||
: `Error: ${response.error}`,
|
||||
is_error: !response.success,
|
||||
}
|
||||
|
|
@ -145,7 +169,10 @@ export class ClaudeMemoryTool {
|
|||
/**
|
||||
* View command: List directory contents or read file with optional line range
|
||||
*/
|
||||
private async view(path: string, viewRange?: [number, number]): Promise<MemoryResponse> {
|
||||
private async view(
|
||||
path: string,
|
||||
viewRange?: [number, number],
|
||||
): Promise<MemoryResponse> {
|
||||
// If path ends with / or is exactly /memories, it's a directory listing request
|
||||
if (path.endsWith("/") || path === "/memories") {
|
||||
// Normalize path to end with /
|
||||
|
|
@ -202,10 +229,7 @@ export class ClaudeMemoryTool {
|
|||
}
|
||||
|
||||
// Format directory listing
|
||||
const entries = [
|
||||
...Array.from(dirs).sort(),
|
||||
...files.sort()
|
||||
]
|
||||
const entries = [...Array.from(dirs).sort(), ...files.sort()]
|
||||
|
||||
if (entries.length === 0) {
|
||||
return {
|
||||
|
|
@ -216,7 +240,7 @@ export class ClaudeMemoryTool {
|
|||
|
||||
return {
|
||||
success: true,
|
||||
content: `Directory: ${dirPath}\n${entries.map(entry => `- ${entry}`).join('\n')}`,
|
||||
content: `Directory: ${dirPath}\n${entries.map((entry) => `- ${entry}`).join("\n")}`,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
|
|
@ -229,7 +253,10 @@ export class ClaudeMemoryTool {
|
|||
/**
|
||||
* Read file contents with optional line range
|
||||
*/
|
||||
private async readFile(filePath: string, viewRange?: [number, number]): Promise<MemoryResponse> {
|
||||
private async readFile(
|
||||
filePath: string,
|
||||
viewRange?: [number, number],
|
||||
): Promise<MemoryResponse> {
|
||||
try {
|
||||
const normalizedId = this.normalizePathToCustomId(filePath)
|
||||
|
||||
|
|
@ -241,7 +268,9 @@ export class ClaudeMemoryTool {
|
|||
})
|
||||
|
||||
// Try to find exact match by customId
|
||||
const exactMatch = response.results?.find(r => r.customId === normalizedId)
|
||||
const exactMatch = response.results?.find(
|
||||
(r) => r.documentId === normalizedId,
|
||||
)
|
||||
const document = exactMatch || response.results?.[0]
|
||||
|
||||
if (!document) {
|
||||
|
|
@ -251,29 +280,31 @@ export class ClaudeMemoryTool {
|
|||
}
|
||||
}
|
||||
|
||||
let content = document.raw || document.content || ""
|
||||
let content = document.content || ""
|
||||
|
||||
// Apply line range if specified
|
||||
if (viewRange) {
|
||||
const lines = content.split('\n')
|
||||
const lines = content.split("\n")
|
||||
const [startLine, endLine] = viewRange
|
||||
const selectedLines = lines.slice(startLine - 1, endLine)
|
||||
|
||||
// Format with line numbers
|
||||
const numberedLines = selectedLines.map((line, index) => {
|
||||
const lineNum = startLine + index
|
||||
return `${lineNum.toString().padStart(4)}\t${line}`
|
||||
})
|
||||
const numberedLines = selectedLines.map(
|
||||
(line: string, index: number) => {
|
||||
const lineNum = startLine + index
|
||||
return `${lineNum.toString().padStart(4)}\t${line}`
|
||||
},
|
||||
)
|
||||
|
||||
content = numberedLines.join('\n')
|
||||
content = numberedLines.join("\n")
|
||||
} else {
|
||||
// Format all lines with line numbers
|
||||
const lines = content.split('\n')
|
||||
const lines = content.split("\n")
|
||||
const numberedLines = lines.map((line, index) => {
|
||||
const lineNum = index + 1
|
||||
return `${lineNum.toString().padStart(4)}\t${line}`
|
||||
})
|
||||
content = numberedLines.join('\n')
|
||||
content = numberedLines.join("\n")
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -291,7 +322,10 @@ export class ClaudeMemoryTool {
|
|||
/**
|
||||
* Create command: Create or overwrite a memory file
|
||||
*/
|
||||
private async create(filePath: string, fileText: string): Promise<MemoryResponse> {
|
||||
private async create(
|
||||
filePath: string,
|
||||
fileText: string,
|
||||
): Promise<MemoryResponse> {
|
||||
try {
|
||||
const normalizedId = this.normalizePathToCustomId(filePath)
|
||||
|
||||
|
|
@ -302,7 +336,7 @@ export class ClaudeMemoryTool {
|
|||
metadata: {
|
||||
claude_memory_type: "file",
|
||||
file_path: filePath,
|
||||
line_count: fileText.split('\n').length,
|
||||
line_count: fileText.split("\n").length,
|
||||
created_by: "claude_memory_tool",
|
||||
last_modified: new Date().toISOString(),
|
||||
},
|
||||
|
|
@ -323,7 +357,11 @@ export class ClaudeMemoryTool {
|
|||
/**
|
||||
* String replace command: Replace text in existing file
|
||||
*/
|
||||
private async strReplace(filePath: string, oldStr: string, newStr: string): Promise<MemoryResponse> {
|
||||
private async strReplace(
|
||||
filePath: string,
|
||||
oldStr: string,
|
||||
newStr: string,
|
||||
): Promise<MemoryResponse> {
|
||||
try {
|
||||
// First, find and read the existing file
|
||||
const readResult = await this.getFileDocument(filePath)
|
||||
|
|
@ -334,7 +372,8 @@ export class ClaudeMemoryTool {
|
|||
}
|
||||
}
|
||||
|
||||
const originalContent = readResult.document.raw || readResult.document.content || ""
|
||||
const originalContent =
|
||||
readResult.document.raw || readResult.document.content || ""
|
||||
|
||||
// Check if old_str exists in the content
|
||||
if (!originalContent.includes(oldStr)) {
|
||||
|
|
@ -355,7 +394,7 @@ export class ClaudeMemoryTool {
|
|||
containerTags: this.containerTags,
|
||||
metadata: {
|
||||
...readResult.document.metadata,
|
||||
line_count: newContent.split('\n').length,
|
||||
line_count: newContent.split("\n").length,
|
||||
last_modified: new Date().toISOString(),
|
||||
},
|
||||
})
|
||||
|
|
@ -375,7 +414,11 @@ export class ClaudeMemoryTool {
|
|||
/**
|
||||
* Insert command: Insert text at specific line
|
||||
*/
|
||||
private async insert(filePath: string, insertLine: number, insertText: string): Promise<MemoryResponse> {
|
||||
private async insert(
|
||||
filePath: string,
|
||||
insertLine: number,
|
||||
insertText: string,
|
||||
): Promise<MemoryResponse> {
|
||||
try {
|
||||
// First, find and read the existing file
|
||||
const readResult = await this.getFileDocument(filePath)
|
||||
|
|
@ -386,8 +429,9 @@ export class ClaudeMemoryTool {
|
|||
}
|
||||
}
|
||||
|
||||
const originalContent = readResult.document.raw || readResult.document.content || ""
|
||||
const lines = originalContent.split('\n')
|
||||
const originalContent =
|
||||
readResult.document.raw || readResult.document.content || ""
|
||||
const lines = originalContent.split("\n")
|
||||
|
||||
// Validate line number
|
||||
if (insertLine < 1 || insertLine > lines.length + 1) {
|
||||
|
|
@ -399,7 +443,7 @@ export class ClaudeMemoryTool {
|
|||
|
||||
// Insert the text (insertLine is 1-based)
|
||||
lines.splice(insertLine - 1, 0, insertText)
|
||||
const newContent = lines.join('\n')
|
||||
const newContent = lines.join("\n")
|
||||
|
||||
// Update the document
|
||||
const normalizedId = this.normalizePathToCustomId(filePath)
|
||||
|
|
@ -409,7 +453,7 @@ export class ClaudeMemoryTool {
|
|||
containerTags: this.containerTags,
|
||||
metadata: {
|
||||
...readResult.document.metadata,
|
||||
line_count: newContent.split('\n').length,
|
||||
line_count: newContent.split("\n").length,
|
||||
last_modified: new Date().toISOString(),
|
||||
},
|
||||
})
|
||||
|
|
@ -459,7 +503,10 @@ export class ClaudeMemoryTool {
|
|||
/**
|
||||
* Rename command: Move/rename memory file
|
||||
*/
|
||||
private async rename(oldPath: string, newPath: string): Promise<MemoryResponse> {
|
||||
private async rename(
|
||||
oldPath: string,
|
||||
newPath: string,
|
||||
): Promise<MemoryResponse> {
|
||||
try {
|
||||
// Validate new path
|
||||
if (!this.isValidPath(newPath)) {
|
||||
|
|
@ -478,7 +525,8 @@ export class ClaudeMemoryTool {
|
|||
}
|
||||
}
|
||||
|
||||
const originalContent = readResult.document.raw || readResult.document.content || ""
|
||||
const originalContent =
|
||||
readResult.document.raw || readResult.document.content || ""
|
||||
const newNormalizedId = this.normalizePathToCustomId(newPath)
|
||||
|
||||
// Create new document with new path
|
||||
|
|
@ -526,7 +574,9 @@ export class ClaudeMemoryTool {
|
|||
})
|
||||
|
||||
// Try to find exact match by customId first
|
||||
const exactMatch = response.results?.find(r => r.customId === normalizedId)
|
||||
const exactMatch = response.results?.find(
|
||||
(r) => r.documentId === normalizedId,
|
||||
)
|
||||
const document = exactMatch || response.results?.[0]
|
||||
|
||||
if (!document) {
|
||||
|
|
@ -552,13 +602,20 @@ export class ClaudeMemoryTool {
|
|||
* Validate that path starts with /memories for security
|
||||
*/
|
||||
private isValidPath(path: string): boolean {
|
||||
return (path.startsWith("/memories/") || path === "/memories") && !path.includes("../") && !path.includes("..\\")
|
||||
return (
|
||||
(path.startsWith("/memories/") || path === "/memories") &&
|
||||
!path.includes("../") &&
|
||||
!path.includes("..\\")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Claude memory tool instance
|
||||
*/
|
||||
export function createClaudeMemoryTool(apiKey: string, config?: ClaudeMemoryConfig) {
|
||||
export function createClaudeMemoryTool(
|
||||
apiKey: string,
|
||||
config?: ClaudeMemoryConfig,
|
||||
) {
|
||||
return new ClaudeMemoryTool(apiKey, config)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue