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
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Platform">
|
||||
Before you begin, ensure you have the required dependencies installed:
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
@@ -30,6 +55,8 @@ pip install mem0ai openai
|
||||
|
||||
The following example shows how to create a basic email processing system with Mem0:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Platform">
|
||||
```python
|
||||
import os
|
||||
from mem0 import MemoryClient
|
||||
@@ -45,11 +72,11 @@ class EmailProcessor:
|
||||
def __init__(self):
|
||||
"""Initialize the Email Processor with Mem0 memory client"""
|
||||
self.client = client
|
||||
|
||||
|
||||
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
|
||||
@@ -57,20 +84,20 @@ class EmailProcessor:
|
||||
# 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
|
||||
metadata = {
|
||||
"email_type": "incoming",
|
||||
@@ -79,18 +106,18 @@ class EmailProcessor:
|
||||
"subject": subject,
|
||||
"date": date
|
||||
}
|
||||
|
||||
|
||||
# Store in Mem0 with appropriate categories
|
||||
response = self.client.add(
|
||||
messages=[message],
|
||||
user_id=user_id,
|
||||
metadata=metadata,
|
||||
categories=["email", "correspondence"],
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def _get_email_body(self, email):
|
||||
"""Extract the body content from an email"""
|
||||
# Simplified extraction - in real-world, handle multipart emails
|
||||
@@ -100,7 +127,7 @@ class EmailProcessor:
|
||||
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
|
||||
@@ -132,7 +159,7 @@ class EmailProcessor:
|
||||
results = self.client.search(query=query, filters=filters)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def get_email_thread(self, subject, user_id):
|
||||
"""
|
||||
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)
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user