Compare commits

...

2 Commits

Author SHA1 Message Date
Devin AI
bbf012e800 Fix lint issues: remove unused imports
- Remove unused 'os' import from reproduce_issue.py
- Remove unused imports (os, Agent, Crew, Task) from test_mem0_storage.py
- Addresses lint check failure in CI

Co-Authored-By: Jo\u00E3o <joao@crewai.com>
2025-07-14 05:13:00 +00:00
Devin AI
46ad20b9f6 Fix mem0 external memory format issue #3152
- Convert string values to proper message format for mem0 API
- mem0 API expects messages as list of objects with role/content fields
- Add comprehensive tests for external memory type and message formatting
- Add reproduction script to verify the fix works
- Resolves 'Expected a list of items but got type str' error

Co-Authored-By: Jo\u00E3o <joao@crewai.com>
2025-07-14 05:09:59 +00:00
4 changed files with 152 additions and 7 deletions

54
reproduce_issue.py Normal file
View File

@@ -0,0 +1,54 @@
"""
Reproduction script for issue #3152 - mem0 external memory format error
Based on the code provided in the GitHub issue
"""
from crewai import Agent, Task, Crew
from crewai.memory.external.external_memory import ExternalMemory
def test_mem0_external_memory():
"""Test that reproduces the mem0 external memory format error"""
embedder_config = {
"provider": "mem0",
"config": {
"user_id": "test_user_123",
}
}
external_memory = ExternalMemory(embedder_config=embedder_config)
agent = Agent(
role="Test Agent",
goal="Test external memory functionality",
backstory="A test agent for reproducing the mem0 issue",
verbose=True
)
task = Task(
description="Test task for external memory",
expected_output="Test output",
agent=agent
)
crew = Crew(
agents=[agent],
tasks=[task],
external_memory=external_memory,
verbose=True
)
print("Testing mem0 external memory integration...")
try:
result = crew.kickoff()
print("SUCCESS: External memory integration worked!")
print(f"Result: {result}")
except Exception as e:
print(f"ERROR: {e}")
if "Expected a list of items but got type" in str(e):
print("CONFIRMED: This is the mem0 format error from issue #3152")
raise
if __name__ == "__main__":
test_mem0_external_memory()

View File

@@ -93,7 +93,13 @@ class Mem0Storage(Storage):
if params: if params:
if isinstance(self.memory, MemoryClient): if isinstance(self.memory, MemoryClient):
params["output_format"] = "v1.1" params["output_format"] = "v1.1"
self.memory.add(value, **params)
if isinstance(value, str):
messages = [{"role": "assistant", "content": value}]
else:
messages = value
self.memory.add(messages, **params)
def search( def search(
self, self,

View File

@@ -329,3 +329,29 @@ def test_external_memory_save_events(custom_storage, external_memory_with_mocked
'agent_role': "test_agent", 'agent_role': "test_agent",
'save_time_ms': ANY 'save_time_ms': ANY
} }
def test_external_memory_with_mem0_storage_integration():
"""Test external memory integration with mem0 storage specifically"""
from crewai.memory.storage.mem0_storage import Mem0Storage
with patch('crewai.memory.external.external_memory.ExternalMemory._configure_mem0') as mock_configure:
mock_storage = MagicMock(spec=Mem0Storage)
mock_configure.return_value = mock_storage
embedder_config = {"provider": "mem0", "config": {"user_id": "test_user"}}
external_memory = ExternalMemory(embedder_config=embedder_config)
mock_crew = MagicMock()
external_memory.set_crew(mock_crew)
test_value = "Test external memory content"
test_metadata = {"task": "test_task"}
test_agent = "test_agent"
external_memory.save(value=test_value, metadata=test_metadata, agent=test_agent)
mock_storage.save.assert_called_once_with(
test_value,
{'task': 'test_task', 'agent': 'test_agent'}
)

View File

@@ -1,14 +1,10 @@
import os
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest import pytest
from mem0.client.main import MemoryClient from mem0.client.main import MemoryClient
from mem0.memory.main import Memory 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.memory.storage.mem0_storage import Mem0Storage
from crewai.task import Task
# Define the class (if not already defined) # Define the class (if not already defined)
@@ -171,8 +167,9 @@ def test_save_method_with_memory_oss(mem0_storage_with_mocked_config):
mem0_storage.save(test_value, test_metadata) mem0_storage.save(test_value, test_metadata)
expected_messages = [{"role": "assistant", "content": test_value}]
mem0_storage.memory.add.assert_called_once_with( mem0_storage.memory.add.assert_called_once_with(
test_value, expected_messages,
agent_id="Test_Agent", agent_id="Test_Agent",
infer=False, infer=False,
metadata={"type": "short_term", "key": "value"}, metadata={"type": "short_term", "key": "value"},
@@ -190,8 +187,9 @@ def test_save_method_with_memory_client(mem0_storage_with_memory_client_using_co
mem0_storage.save(test_value, test_metadata) mem0_storage.save(test_value, test_metadata)
expected_messages = [{"role": "assistant", "content": test_value}]
mem0_storage.memory.add.assert_called_once_with( mem0_storage.memory.add.assert_called_once_with(
test_value, expected_messages,
agent_id="Test_Agent", agent_id="Test_Agent",
infer=False, infer=False,
metadata={"type": "short_term", "key": "value"}, metadata={"type": "short_term", "key": "value"},
@@ -218,6 +216,67 @@ def test_search_method_with_memory_oss(mem0_storage_with_mocked_config):
assert results[0]["content"] == "Result 1" assert results[0]["content"] == "Result 1"
def test_save_method_external_memory_type():
"""Test save method specifically for external memory type"""
crew = MockCrew(
memory_config={
"provider": "mem0",
"config": {"user_id": "test_user", "api_key": "test-key"},
}
)
with patch.object(MemoryClient, "__new__") as mock_client:
mock_memory_instance = MagicMock(spec=MemoryClient)
mock_client.return_value = mock_memory_instance
mem0_storage = Mem0Storage(type="external", crew=crew)
mem0_storage.memory.add = MagicMock()
test_value = "External memory test content"
test_metadata = {"task": "test_task", "agent": "test_agent"}
mem0_storage.save(test_value, test_metadata)
expected_messages = [{"role": "assistant", "content": test_value}]
mem0_storage.memory.add.assert_called_once_with(
expected_messages,
user_id="test_user",
agent_id="Test_Agent",
metadata={"type": "external", "task": "test_task", "agent": "test_agent"},
output_format="v1.1"
)
def test_save_method_with_non_string_value():
"""Test save method when value is already in message format"""
crew = MockCrew(
memory_config={
"provider": "mem0",
"config": {"user_id": "test_user", "api_key": "test-key"},
}
)
with patch.object(MemoryClient, "__new__") as mock_client:
mock_memory_instance = MagicMock(spec=MemoryClient)
mock_client.return_value = mock_memory_instance
mem0_storage = Mem0Storage(type="external", crew=crew)
mem0_storage.memory.add = MagicMock()
test_messages = [{"role": "user", "content": "Test message"}]
test_metadata = {"task": "test_task"}
mem0_storage.save(test_messages, test_metadata)
mem0_storage.memory.add.assert_called_once_with(
test_messages,
user_id="test_user",
agent_id="Test_Agent",
metadata={"type": "external", "task": "test_task"},
output_format="v1.1"
)
def test_search_method_with_memory_client(mem0_storage_with_memory_client_using_config_from_crew): def test_search_method_with_memory_client(mem0_storage_with_memory_client_using_config_from_crew):
"""Test search method for different memory types""" """Test search method for different memory types"""
mem0_storage = mem0_storage_with_memory_client_using_config_from_crew mem0_storage = mem0_storage_with_memory_client_using_config_from_crew