Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
36f0825
add Mem0 OSS support
parthbs Mar 20, 2025
b2a2cb5
Merge branch 'main' into feat/mem0-memory-support
parthbs Mar 20, 2025
ab1fa4d
Merge branch 'main' into feat/mem0-memory-support
parthbs Mar 21, 2025
3b5b462
Extended Mem0 functionaliy to include Local Mem0 as well
Vidit-Ostwal Mar 21, 2025
2ae77da
Ruff check
Vidit-Ostwal Mar 21, 2025
4d737ec
Fixed test case to compare just class type
Vidit-Ostwal Mar 21, 2025
9d5d357
Added _validate_local_mem0_config
Vidit-Ostwal Mar 21, 2025
c349f36
Ruff check
Vidit-Ostwal Mar 21, 2025
d3e2af7
typo error
Vidit-Ostwal Mar 21, 2025
5634ffc
Fixed test case
Vidit-Ostwal Mar 21, 2025
2eb5793
Test Cases fixing #1
Vidit-Ostwal Mar 21, 2025
ddb6f23
Test Cases fixing #2
Vidit-Ostwal Mar 21, 2025
41ace01
Fixed test case #3
Vidit-Ostwal Mar 21, 2025
2d73f6f
Fixed test case #3
Vidit-Ostwal Mar 21, 2025
5828f44
Ruff check
Vidit-Ostwal Mar 21, 2025
e8d6198
Test Case fix #5
Vidit-Ostwal Mar 21, 2025
61e186b
Fixed test case #6
Vidit-Ostwal Mar 21, 2025
21d2884
Fixed test case --last try
Vidit-Ostwal Mar 21, 2025
e3aa1f9
Added support to pass config to Memory()
Vidit-Ostwal Mar 21, 2025
f57eb35
Resolved linting issue
Vidit-Ostwal Mar 21, 2025
4829269
Issue with test run
Vidit-Ostwal Mar 21, 2025
4344b61
test cases failing
Vidit-Ostwal Mar 21, 2025
396d5f8
Merge remote-tracking branch 'upstream/main' into Branch_2426
Vidit-Ostwal Mar 22, 2025
06231f0
Fixed test cases, used patch.object instead of direct patching
Vidit-Ostwal Mar 22, 2025
79d3d26
Merge branch 'main' into Branch_2426
Vidit-Ostwal Mar 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions docs/concepts/memory.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ crew = Crew(

[Mem0](https://mem0.ai/) is a self-improving memory layer for LLM applications, enabling personalized AI experiences.


#### Using Mem0 API platform

To include user-specific memory you can get your API key [here](https://app.mem0.ai/dashboard/api-keys) and refer the [docs](https://docs.mem0.ai/platform/quickstart#4-1-create-memories) for adding user preferences.


Expand Down Expand Up @@ -219,6 +222,66 @@ crew = Crew(
)
```

#### Using Local Mem0 memory
If you want to use local mem0 memory, with a custom configuration, you can set a parameter `local_mem0_config` in the config itself.
The API platform takes higher priority over the local configuration.
Check [this](https://docs.mem0.ai/open-source/python-quickstart#run-mem0-locally) mem0 local configuration docs for more understanding.

```python Code
from crewai import Crew


#local mem0 config
config = {
"vector_store": {
"provider": "qdrant",
"config": {
"host": "localhost",
"port": 6333
}
},
"llm": {
"provider": "openai",
"config": {
"api_key": "your-api-key",
"model": "gpt-4"
}
},
"embedder": {
"provider": "openai",
"config": {
"api_key": "your-api-key",
"model": "text-embedding-3-small"
}
},
"graph_store": {
"provider": "neo4j",
"config": {
"url": "neo4j+s://your-instance",
"username": "neo4j",
"password": "password"
}
},
"history_db_path": "/path/to/history.db",
"version": "v1.1",
"custom_fact_extraction_prompt": "Optional custom prompt for fact extraction for memory",
"custom_update_memory_prompt": "Optional custom prompt for update memory"
}

crew = Crew(
agents=[...],
tasks=[...],
verbose=True,
memory=True,
memory_config={
"provider": "mem0",
"config": {"user_id": "john", 'local_mem0_config': config},
},
)
```



## Additional Embedding Providers

### Using OpenAI embeddings (already default)
Expand Down
89 changes: 75 additions & 14 deletions src/crewai/memory/storage/mem0_storage.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
import os
from typing import Any, Dict, List

Expand Down Expand Up @@ -31,6 +32,7 @@ def __init__(self, type, crew=None):
mem0_api_key = config.get("api_key") or os.getenv("MEM0_API_KEY")
mem0_org_id = config.get("org_id")
mem0_project_id = config.get("project_id")
local_mem0_config = config.get("local_mem0_config")

# Initialize MemoryClient or Memory based on the presence of the mem0_api_key
if mem0_api_key:
Expand All @@ -41,14 +43,33 @@ def __init__(self, type, crew=None):
else:
self.memory = MemoryClient(api_key=mem0_api_key)
else:
self.memory = Memory() # Fallback to Memory if no Mem0 API key is provided
# Fallback to Memory if no Mem0 API key is provided
if local_mem0_config is None:
self.memory = Memory()
else:
self._validate_local_mem0_config(local_mem0_config)
self.memory = Memory.from_config(local_mem0_config)

def _sanitize_role(self, role: str) -> str:
"""
Sanitizes agent roles to ensure valid directory names.
"""
return role.replace("\n", "").replace(" ", "_").replace("/", "_")

def _get_user_id(self):
if self.memory_type == "user":
if hasattr(self, "memory_config") and self.memory_config is not None:
return self.memory_config.get("config", {}).get("user_id")
else:
return None
return None

def _get_agent_name(self):
agents = self.crew.agents if self.crew else []
agents = [self._sanitize_role(agent.role) for agent in agents]
agents = "_".join(agents)
return agents

def save(self, value: Any, metadata: Dict[str, Any]) -> None:
user_id = self._get_user_id()
agent_name = self._get_agent_name()
Expand Down Expand Up @@ -101,16 +122,56 @@ def search(
results = self.memory.search(**params)
return [r for r in results if r["score"] >= score_threshold]

def _get_user_id(self):
if self.memory_type == "user":
if hasattr(self, "memory_config") and self.memory_config is not None:
return self.memory_config.get("config", {}).get("user_id")
else:
return None
return None

def _get_agent_name(self):
agents = self.crew.agents if self.crew else []
agents = [self._sanitize_role(agent.role) for agent in agents]
agents = "_".join(agents)
return agents
def reset(self) -> None:
"""
Resets the memory by clearing all stored data.
"""
try:
self.memory.reset()
except Exception as e:
raise Exception(
f"An error occurred while resetting the {self.memory_type} : {e}"
)

def _validate_local_mem0_config(self, config: Dict[str, Any]) -> bool:
"""
Validates that all keys in the config are allowed keys from the default configuration.
Raises an exception if an invalid key is found.

Args:
config: The configuration dictionary to validate

Returns:
bool: True if all keys in the configuration are valid

Raises:
ValueError: If any invalid keys are found in the configuration
"""
# Define allowed keys at each level
allowed_keys = {
"root": ["vector_store", "llm", "embedder", "graph_store", "history_db_path",
"version", "custom_fact_extraction_prompt", "custom_update_memory_prompt"],
"vector_store": ["provider", "config"],
"vector_store.config": ["host", "port"],
"llm": ["provider", "config"],
"llm.config": ["api_key", "model"],
"embedder": ["provider", "config"],
"embedder.config": ["api_key", "model"],
"graph_store": ["provider", "config"],
"graph_store.config": ["url", "username", "password"]
}

def check_keys(d, path="root"):
# Check if all keys at this level are allowed
for key in d:
if key not in allowed_keys.get(path, []):
invalid_key = f"{path}.{key}" if path != "root" else key
raise ValueError(f"Invalid configuration key: '{invalid_key}'. Please refer to the Mem0 documentation for valid configuration parameters: https://docs.mem0.ai/open-source/python-quickstart#configuration-parameters")

# If value is a dict, check its keys recursively
if isinstance(d[key], dict):
new_path = f"{path}.{key}" if path != "root" else key
check_keys(d[key], new_path)

return True
return check_keys(config)
114 changes: 114 additions & 0 deletions tests/storage/test_mem0_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import os
from unittest.mock import MagicMock, patch

import pytest
from mem0.client.main import MemoryClient
from mem0.memory.main import Memory

from crewai.agent import Agent
from crewai.crew import Crew
from crewai.memory.storage.mem0_storage import Mem0Storage
from crewai.task import Task


# Define the class (if not already defined)
class MockCrew:
def __init__(self, memory_config):
self.memory_config = memory_config


@pytest.fixture
def mock_mem0_memory():
"""Fixture to create a mock Memory instance"""
mock_memory = MagicMock(spec=Memory)
return mock_memory


@pytest.fixture
def mem0_storage_with_mocked_config(mock_mem0_memory):
"""Fixture to create a Mem0Storage instance with mocked dependencies"""

# Patch the Memory class to return our mock
with patch('mem0.memory.main.Memory.from_config', return_value=mock_mem0_memory):
config = {
"vector_store": {
"provider": "mock_vector_store",
"config": {
"host": "localhost",
"port": 6333
}
},
"llm": {
"provider": "mock_llm",
"config": {
"api_key": "mock-api-key",
"model": "mock-model"
}
},
"embedder": {
"provider": "mock_embedder",
"config": {
"api_key": "mock-api-key",
"model": "mock-model"
}
},
"graph_store": {
"provider": "mock_graph_store",
"config": {
"url": "mock-url",
"username": "mock-user",
"password": "mock-password"
}
},
"history_db_path": "/mock/path",
"version": "test-version",
"custom_fact_extraction_prompt": "mock prompt 1",
"custom_update_memory_prompt": "mock prompt 2"
}

# Instantiate the class with memory_config
crew = MockCrew(
memory_config={
"provider": "mem0",
"config": {"user_id": "test_user", "local_mem0_config": config},
}
)

mem0_storage = Mem0Storage(type="short_term", crew=crew)
return mem0_storage


def test_mem0_storage_initialization(mem0_storage_with_mocked_config, mock_mem0_memory):
"""Test that Mem0Storage initializes correctly with the mocked config"""
assert mem0_storage_with_mocked_config.memory_type == "short_term"
assert mem0_storage_with_mocked_config.memory is mock_mem0_memory


@pytest.fixture
def mock_mem0_memory_client():
"""Fixture to create a mock MemoryClient instance"""
mock_memory = MagicMock(spec=MemoryClient)
return mock_memory


@pytest.fixture
def mem0_storage_with_memory_client(mock_mem0_memory_client):
"""Fixture to create a Mem0Storage instance with mocked dependencies"""

# We need to patch the MemoryClient before it's instantiated
with patch.object(MemoryClient, '__new__', return_value=mock_mem0_memory_client):
crew = MockCrew(
memory_config={
"provider": "mem0",
"config": {"user_id": "test_user", "api_key": "ABCDEFGH"},
}
)

mem0_storage = Mem0Storage(type="short_term", crew=crew)
return mem0_storage


def test_mem0_storage_with_memory_client_initialization(mem0_storage_with_memory_client, mock_mem0_memory_client):
"""Test Mem0Storage initialization with MemoryClient"""
assert mem0_storage_with_memory_client.memory_type == "short_term"
assert mem0_storage_with_memory_client.memory is mock_mem0_memory_client