docs: add installation and configuration guide for email automation with Mem0 open source (#4567)
This commit is contained in:
@@ -18,11 +18,36 @@ Email overload is a common challenge for many professionals. By leveraging Mem0'
|
|||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Platform">
|
||||||
Before you begin, ensure you have the required dependencies installed:
|
Before you begin, ensure you have the required dependencies installed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install mem0ai openai
|
pip install mem0ai openai
|
||||||
```
|
```
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Open Source">
|
||||||
|
Here we use **Mem0 open source** (`Memory`): all local, no API keys needed for memory. Vectors in **Qdrant**, LLM and embeddings via **Ollama**.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Install the required dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install mem0ai qdrant-client openai ollama
|
||||||
|
```
|
||||||
|
|
||||||
|
Then start Qdrant and pull the Ollama models:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d -p 6333:6333 qdrant/qdrant
|
||||||
|
ollama pull llama3.1:latest
|
||||||
|
ollama pull nomic-embed-text:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
<Note>You can swap `nomic-embed-text` for any Ollama-supported embedding model (e.g., `snowflake-arctic-embed`, `mxbai-embed-large`). Just update the `model` in the `embedder` config and set `embedding_model_dims` in the Qdrant config to match the model's output dimensions (768 for `nomic-embed-text`).</Note>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
## Implementation
|
## Implementation
|
||||||
|
|
||||||
@@ -30,6 +55,8 @@ pip install mem0ai openai
|
|||||||
|
|
||||||
The following example shows how to create a basic email processing system with Mem0:
|
The following example shows how to create a basic email processing system with Mem0:
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Platform">
|
||||||
```python
|
```python
|
||||||
import os
|
import os
|
||||||
from mem0 import MemoryClient
|
from mem0 import MemoryClient
|
||||||
@@ -45,11 +72,11 @@ class EmailProcessor:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize the Email Processor with Mem0 memory client"""
|
"""Initialize the Email Processor with Mem0 memory client"""
|
||||||
self.client = client
|
self.client = client
|
||||||
|
|
||||||
def process_email(self, email_content, user_id):
|
def process_email(self, email_content, user_id):
|
||||||
"""
|
"""
|
||||||
Process an email and store it in Mem0 memory
|
Process an email and store it in Mem0 memory
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
email_content (str): Raw email content
|
email_content (str): Raw email content
|
||||||
user_id (str): User identifier for memory association
|
user_id (str): User identifier for memory association
|
||||||
@@ -57,20 +84,20 @@ class EmailProcessor:
|
|||||||
# Parse email
|
# Parse email
|
||||||
parser = Parser()
|
parser = Parser()
|
||||||
email = parser.parsestr(email_content)
|
email = parser.parsestr(email_content)
|
||||||
|
|
||||||
# Extract email details
|
# Extract email details
|
||||||
sender = email['from']
|
sender = email['from']
|
||||||
recipient = email['to']
|
recipient = email['to']
|
||||||
subject = email['subject']
|
subject = email['subject']
|
||||||
date = email['date']
|
date = email['date']
|
||||||
body = self._get_email_body(email)
|
body = self._get_email_body(email)
|
||||||
|
|
||||||
# Create message object for Mem0
|
# Create message object for Mem0
|
||||||
message = {
|
message = {
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": f"Email from {sender}: {subject}\n\n{body}"
|
"content": f"Email from {sender}: {subject}\n\n{body}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create metadata for better retrieval
|
# Create metadata for better retrieval
|
||||||
metadata = {
|
metadata = {
|
||||||
"email_type": "incoming",
|
"email_type": "incoming",
|
||||||
@@ -79,18 +106,18 @@ class EmailProcessor:
|
|||||||
"subject": subject,
|
"subject": subject,
|
||||||
"date": date
|
"date": date
|
||||||
}
|
}
|
||||||
|
|
||||||
# Store in Mem0 with appropriate categories
|
# Store in Mem0 with appropriate categories
|
||||||
response = self.client.add(
|
response = self.client.add(
|
||||||
messages=[message],
|
messages=[message],
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
categories=["email", "correspondence"],
|
categories=["email", "correspondence"],
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _get_email_body(self, email):
|
def _get_email_body(self, email):
|
||||||
"""Extract the body content from an email"""
|
"""Extract the body content from an email"""
|
||||||
# Simplified extraction - in real-world, handle multipart emails
|
# Simplified extraction - in real-world, handle multipart emails
|
||||||
@@ -100,7 +127,7 @@ class EmailProcessor:
|
|||||||
return part.get_payload(decode=True).decode()
|
return part.get_payload(decode=True).decode()
|
||||||
else:
|
else:
|
||||||
return email.get_payload(decode=True).decode()
|
return email.get_payload(decode=True).decode()
|
||||||
|
|
||||||
def search_emails(self, query, user_id, sender=None):
|
def search_emails(self, query, user_id, sender=None):
|
||||||
"""
|
"""
|
||||||
Search through stored emails
|
Search through stored emails
|
||||||
@@ -132,7 +159,7 @@ class EmailProcessor:
|
|||||||
results = self.client.search(query=query, filters=filters)
|
results = self.client.search(query=query, filters=filters)
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def get_email_thread(self, subject, user_id):
|
def get_email_thread(self, subject, user_id):
|
||||||
"""
|
"""
|
||||||
Retrieve all emails in a thread based on subject
|
Retrieve all emails in a thread based on subject
|
||||||
@@ -182,6 +209,208 @@ processor.process_email(sample_email, user_id)
|
|||||||
meeting_emails = processor.search_emails("meeting schedule", user_id)
|
meeting_emails = processor.search_emails("meeting schedule", user_id)
|
||||||
print(f"Found {len(meeting_emails['results'])} relevant emails")
|
print(f"Found {len(meeting_emails['results'])} relevant emails")
|
||||||
```
|
```
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Open Source">
|
||||||
|
```python
|
||||||
|
from mem0 import Memory
|
||||||
|
from email.parser import Parser
|
||||||
|
|
||||||
|
OLLAMA_URL = "http://localhost:11434"
|
||||||
|
|
||||||
|
# Set up Mem0 with local providers
|
||||||
|
memory = Memory.from_config({
|
||||||
|
"vector_store": {
|
||||||
|
"provider": "qdrant",
|
||||||
|
"config": {
|
||||||
|
"collection_name": "email_intelligence",
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 6333,
|
||||||
|
"embedding_model_dims": 768,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"llm": {
|
||||||
|
"provider": "ollama",
|
||||||
|
"config": {
|
||||||
|
"model": "llama3.1:latest",
|
||||||
|
"temperature": 0,
|
||||||
|
"max_tokens": 2000,
|
||||||
|
"ollama_base_url": OLLAMA_URL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"embedder": {
|
||||||
|
"provider": "ollama",
|
||||||
|
"config": {
|
||||||
|
"model": "nomic-embed-text:latest",
|
||||||
|
"ollama_base_url": OLLAMA_URL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
class EmailProcessor:
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the Email Processor with Mem0 memory"""
|
||||||
|
self.memory = memory
|
||||||
|
|
||||||
|
def process_email(self, email_content, user_id):
|
||||||
|
"""
|
||||||
|
Process an email and store it in Mem0 memory
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email_content (str): Raw email content
|
||||||
|
user_id (str): User identifier for memory association
|
||||||
|
"""
|
||||||
|
# Parse email
|
||||||
|
parser = Parser()
|
||||||
|
email = parser.parsestr(email_content)
|
||||||
|
|
||||||
|
# Extract email details
|
||||||
|
sender = email["from"]
|
||||||
|
recipient = email["to"]
|
||||||
|
subject = email["subject"]
|
||||||
|
date = email["date"]
|
||||||
|
body = self._get_email_body(email)
|
||||||
|
|
||||||
|
# Create message object for Mem0
|
||||||
|
message = {
|
||||||
|
"role": "user",
|
||||||
|
"content": f"Email from {sender}: {subject}\n\n{body}",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create metadata for better retrieval
|
||||||
|
# In OSS, categories are modeled as metadata fields
|
||||||
|
metadata = {
|
||||||
|
"email_type": "incoming",
|
||||||
|
"memory_category": "email",
|
||||||
|
"sender": sender,
|
||||||
|
"recipient": recipient,
|
||||||
|
"subject": subject,
|
||||||
|
"date": date,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Store in Mem0
|
||||||
|
response = self.memory.add(
|
||||||
|
message,
|
||||||
|
user_id=user_id,
|
||||||
|
metadata=metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _get_email_body(self, email):
|
||||||
|
"""Extract the body content from an email"""
|
||||||
|
if email.is_multipart():
|
||||||
|
for part in email.walk():
|
||||||
|
if part.get_content_type() == "text/plain":
|
||||||
|
return part.get_payload(decode=True).decode()
|
||||||
|
else:
|
||||||
|
return email.get_payload(decode=True).decode()
|
||||||
|
|
||||||
|
def search_emails(self, query, user_id, sender=None):
|
||||||
|
"""
|
||||||
|
Search through stored emails
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (str): Search query
|
||||||
|
user_id (str): User identifier
|
||||||
|
sender (str, optional): Filter by sender email address
|
||||||
|
"""
|
||||||
|
# In OSS, user_id is an explicit parameter (not inside filters)
|
||||||
|
if not sender:
|
||||||
|
results = self.memory.search(
|
||||||
|
query=query,
|
||||||
|
user_id=user_id,
|
||||||
|
filters={"memory_category": "email"},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
results = self.memory.search(
|
||||||
|
query=query,
|
||||||
|
user_id=user_id,
|
||||||
|
filters={
|
||||||
|
"AND": [
|
||||||
|
{"memory_category": "email"},
|
||||||
|
{"sender": sender},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def get_email_thread(self, subject, user_id):
|
||||||
|
"""
|
||||||
|
Retrieve all emails in a thread based on subject
|
||||||
|
|
||||||
|
Args:
|
||||||
|
subject (str): Email subject to match
|
||||||
|
user_id (str): User identifier
|
||||||
|
"""
|
||||||
|
# In OSS, user_id is an explicit parameter
|
||||||
|
thread = self.memory.get_all(
|
||||||
|
user_id=user_id,
|
||||||
|
filters={
|
||||||
|
"AND": [
|
||||||
|
{"memory_category": "email"},
|
||||||
|
{"subject": {"icontains": subject}},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return thread
|
||||||
|
|
||||||
|
# Initialize the processor
|
||||||
|
processor = EmailProcessor()
|
||||||
|
|
||||||
|
# Example raw email
|
||||||
|
sample_email = """From: alice@example.com
|
||||||
|
To: bob@example.com
|
||||||
|
Subject: Meeting Schedule Update
|
||||||
|
Date: Mon, 15 Jul 2024 14:22:05 -0700
|
||||||
|
|
||||||
|
Hi Bob,
|
||||||
|
|
||||||
|
I wanted to update you on the schedule for our upcoming project meeting.
|
||||||
|
We'll be meeting this Thursday at 2pm instead of Friday.
|
||||||
|
|
||||||
|
Could you please prepare your section of the presentation?
|
||||||
|
|
||||||
|
Thanks,
|
||||||
|
Alice
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Process and store the email
|
||||||
|
user_id = "bob@example.com"
|
||||||
|
processor.process_email(sample_email, user_id)
|
||||||
|
|
||||||
|
# Later, search for emails about meetings
|
||||||
|
meeting_emails = processor.search_emails("meeting schedule", user_id)
|
||||||
|
print(f"Found {len(meeting_emails['results'])} relevant emails")
|
||||||
|
```
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
**Categories vs Metadata:** The Platform version uses `categories=["email"]` which are AI-assigned by Mem0. In open source, categories are modeled as `metadata` fields (e.g., `memory_category`) that you set on each `add` call and filter on during search.
|
||||||
|
</Note>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
### Fetching Memories
|
||||||
|
|
||||||
|
You can fetch all the memories at any point in time using the following code:
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Platform">
|
||||||
|
```python
|
||||||
|
meeting_emails = processor.search_emails("meeting schedule", user_id)
|
||||||
|
for m in meeting_emails['results']:
|
||||||
|
print(m['memory'])
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Open Source">
|
||||||
|
```python
|
||||||
|
meeting_emails = processor.search_emails("meeting schedule", user_id)
|
||||||
|
for m in meeting_emails["results"]:
|
||||||
|
print(m["memory"])
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
## Key Features and Benefits
|
## Key Features and Benefits
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user