Skip to content

Commit 1b00cc7

Browse files
authored
Dropping messages from metadata in Mem0 Storage (#3390)
* Dropped messages from metadata and added user-assistant interaction directly * Fixed test cases for this * Fixed static type checking issue * Changed logic to take latest user and assistant messages * Added default value to be string * Linting checks * Removed duplication of tool calling * Fixed Linting Changes * Ruff check * Removed console formatter file from commit * Linting fixed * Linting checks * Ignoring missing imports error * Added suggested changes * Fixed import untyped error
1 parent 45d0c99 commit 1b00cc7

File tree

2 files changed

+55
-25
lines changed

2 files changed

+55
-25
lines changed

src/crewai/memory/storage/mem0_storage.py

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import os
2-
from typing import Any, Dict, List
2+
import re
33
from collections import defaultdict
4-
from mem0 import Memory, MemoryClient
5-
from crewai.utilities.chromadb import sanitize_collection_name
4+
from typing import Any, Iterable
5+
6+
from mem0 import Memory, MemoryClient # type: ignore[import-untyped]
67

78
from crewai.memory.storage.interface import Storage
9+
from crewai.utilities.chromadb import sanitize_collection_name
810

911
MAX_AGENT_ID_LENGTH_MEM0 = 255
1012

@@ -86,9 +88,28 @@ def _create_filter_for_search(self):
8688

8789
return filter
8890

89-
def save(self, value: Any, metadata: Dict[str, Any]) -> None:
91+
def save(self, value: Any, metadata: dict[str, Any]) -> None:
92+
def _last_content(messages: Iterable[dict[str, Any]], role: str) -> str:
93+
return next(
94+
(m.get("content", "") for m in reversed(list(messages)) if m.get("role") == role),
95+
""
96+
)
97+
98+
conversations = []
99+
messages = metadata.pop("messages", None)
100+
if messages:
101+
last_user = _last_content(messages, "user")
102+
last_assistant = _last_content(messages, "assistant")
103+
104+
if user_msg := self._get_user_message(last_user):
105+
conversations.append({"role": "user", "content": user_msg})
106+
107+
if assistant_msg := self._get_assistant_message(last_assistant):
108+
conversations.append({"role": "assistant", "content": assistant_msg})
109+
else:
110+
conversations.append({"role": "assistant", "content": value})
111+
90112
user_id = self.config.get("user_id", "")
91-
assistant_message = [{"role" : "assistant","content" : value}]
92113

93114
base_metadata = {
94115
"short_term": "short_term",
@@ -119,9 +140,9 @@ def save(self, value: Any, metadata: Dict[str, Any]) -> None:
119140
if agent_id := self.config.get("agent_id", self._get_agent_name()):
120141
params["agent_id"] = agent_id
121142

122-
self.memory.add(assistant_message, **params)
143+
self.memory.add(conversations, **params)
123144

124-
def search(self,query: str,limit: int = 3,score_threshold: float = 0.35) -> List[Any]:
145+
def search(self,query: str,limit: int = 3,score_threshold: float = 0.35) -> list[Any]:
125146
params = {
126147
"query": query,
127148
"limit": limit,
@@ -160,7 +181,7 @@ def search(self,query: str,limit: int = 3,score_threshold: float = 0.35) -> List
160181
# This makes it compatible for Contextual Memory to retrieve
161182
for result in results["results"]:
162183
result["context"] = result["memory"]
163-
184+
164185
return [r for r in results["results"]]
165186

166187
def reset(self):
@@ -181,3 +202,16 @@ def _get_agent_name(self) -> str:
181202
agents = [self._sanitize_role(agent.role) for agent in agents]
182203
agents = "_".join(agents)
183204
return sanitize_collection_name(name=agents, max_collection_length=MAX_AGENT_ID_LENGTH_MEM0)
205+
206+
def _get_assistant_message(self, text: str) -> str:
207+
marker = "Final Answer:"
208+
if marker in text:
209+
return text.split(marker, 1)[1].strip()
210+
return text
211+
212+
def _get_user_message(self, text: str) -> str:
213+
pattern = r"User message:\s*(.*)"
214+
match = re.search(pattern, text)
215+
if match:
216+
return match.group(1).strip()
217+
return text

tests/storage/test_mem0_storage.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ def __init__(self):
1616
@pytest.fixture
1717
def mock_mem0_memory():
1818
"""Fixture to create a mock Memory instance"""
19-
mock_memory = MagicMock(spec=Memory)
20-
return mock_memory
19+
return MagicMock(spec=Memory)
2120

2221

2322
@pytest.fixture
@@ -73,8 +72,7 @@ def test_mem0_storage_initialization(mem0_storage_with_mocked_config, mock_mem0_
7372
@pytest.fixture
7473
def mock_mem0_memory_client():
7574
"""Fixture to create a mock MemoryClient instance"""
76-
mock_memory = MagicMock(spec=MemoryClient)
77-
return mock_memory
75+
return MagicMock(spec=MemoryClient)
7876

7977

8078
@pytest.fixture
@@ -96,8 +94,7 @@ def mem0_storage_with_memory_client_using_config_from_crew(mock_mem0_memory_clie
9694
"infer": True
9795
}
9896

99-
mem0_storage = Mem0Storage(type="short_term", crew=crew, config=embedder_config)
100-
return mem0_storage
97+
return Mem0Storage(type="short_term", crew=crew, config=embedder_config)
10198

10299

103100
@pytest.fixture
@@ -111,8 +108,7 @@ def mem0_storage_with_memory_client_using_explictly_config(mock_mem0_memory_clie
111108
crew = MockCrew()
112109
new_config = {"provider": "mem0", "config": {"api_key": "new-api-key"}}
113110

114-
mem0_storage = Mem0Storage(type="short_term", crew=crew, config=new_config)
115-
return mem0_storage
111+
return Mem0Storage(type="short_term", crew=crew, config=new_config)
116112

117113

118114
def test_mem0_storage_with_memory_client_initialization(
@@ -172,14 +168,14 @@ def test_save_method_with_memory_oss(mem0_storage_with_mocked_config):
172168

173169
# Test short_term memory type (already set in fixture)
174170
test_value = "This is a test memory"
175-
test_metadata = {"key": "value"}
171+
test_metadata = {'description': 'Respond to user conversation. User message: What do you know about me?', 'messages': [{'role': 'system', 'content': 'You are Friendly chatbot assistant. You are a kind and knowledgeable chatbot assistant. You excel at understanding user needs, providing helpful responses, and maintaining engaging conversations. You remember previous interactions to provide a personalized experience.\nYour personal goal is: Engage in useful and interesting conversations with users while remembering context.\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!'}, {'role': 'user', 'content': '\nCurrent Task: Respond to user conversation. User message: What do you know about me?\n\nThis is the expected criteria for your final answer: Contextually appropriate, helpful, and friendly response.\nyou MUST return the actual complete content as the final answer, not a summary.\n\n# Useful context: \nExternal memories:\n- User is from India\n- User is interested in the solar system\n- User name is Vidit Ostwal\n- User is interested in French cuisine\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:'}, {'role': 'assistant', 'content': "I now can give a great answer \nFinal Answer: Hi Vidit! From our previous conversations, I know you're from India and have a great interest in the solar system. It's fascinating to explore the wonders of space, isn't it? Also, I remember you have a passion for French cuisine, which has so many delightful dishes to explore. If there's anything specific you'd like to discuss or learn about—whether it's about the solar system or some great French recipes—feel free to let me know! I'm here to help."}], 'agent': 'Friendly chatbot assistant'}
176172

177173
mem0_storage.save(test_value, test_metadata)
178174

179175
mem0_storage.memory.add.assert_called_once_with(
180-
[{"role": "assistant" , "content": test_value}],
176+
[{'role': 'user', 'content': 'What do you know about me?'}, {'role': 'assistant', 'content': "Hi Vidit! From our previous conversations, I know you're from India and have a great interest in the solar system. It's fascinating to explore the wonders of space, isn't it? Also, I remember you have a passion for French cuisine, which has so many delightful dishes to explore. If there's anything specific you'd like to discuss or learn about—whether it's about the solar system or some great French recipes—feel free to let me know! I'm here to help."}],
181177
infer=True,
182-
metadata={"type": "short_term", "key": "value"},
178+
metadata={'type': 'short_term', 'description': 'Respond to user conversation. User message: What do you know about me?', 'agent': 'Friendly chatbot assistant'},
183179
run_id="my_run_id",
184180
user_id="test_user",
185181
agent_id='Test_Agent'
@@ -191,14 +187,14 @@ def test_save_method_with_multiple_agents(mem0_storage_with_mocked_config):
191187
mem0_storage.memory.add = MagicMock()
192188

193189
test_value = "This is a test memory"
194-
test_metadata = {"key": "value"}
190+
test_metadata = {'description': 'Respond to user conversation. User message: What do you know about me?', 'messages': [{'role': 'system', 'content': 'You are Friendly chatbot assistant. You are a kind and knowledgeable chatbot assistant. You excel at understanding user needs, providing helpful responses, and maintaining engaging conversations. You remember previous interactions to provide a personalized experience.\nYour personal goal is: Engage in useful and interesting conversations with users while remembering context.\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!'}, {'role': 'user', 'content': '\nCurrent Task: Respond to user conversation. User message: What do you know about me?\n\nThis is the expected criteria for your final answer: Contextually appropriate, helpful, and friendly response.\nyou MUST return the actual complete content as the final answer, not a summary.\n\n# Useful context: \nExternal memories:\n- User is from India\n- User is interested in the solar system\n- User name is Vidit Ostwal\n- User is interested in French cuisine\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:'}, {'role': 'assistant', 'content': "I now can give a great answer \nFinal Answer: Hi Vidit! From our previous conversations, I know you're from India and have a great interest in the solar system. It's fascinating to explore the wonders of space, isn't it? Also, I remember you have a passion for French cuisine, which has so many delightful dishes to explore. If there's anything specific you'd like to discuss or learn about—whether it's about the solar system or some great French recipes—feel free to let me know! I'm here to help."}], 'agent': 'Friendly chatbot assistant'}
195191

196192
mem0_storage.save(test_value, test_metadata)
197193

198194
mem0_storage.memory.add.assert_called_once_with(
199-
[{"role": "assistant" , "content": test_value}],
195+
[{'role': 'user', 'content': 'What do you know about me?'}, {'role': 'assistant', 'content': "Hi Vidit! From our previous conversations, I know you're from India and have a great interest in the solar system. It's fascinating to explore the wonders of space, isn't it? Also, I remember you have a passion for French cuisine, which has so many delightful dishes to explore. If there's anything specific you'd like to discuss or learn about—whether it's about the solar system or some great French recipes—feel free to let me know! I'm here to help."}],
200196
infer=True,
201-
metadata={"type": "short_term", "key": "value"},
197+
metadata={'type': 'short_term', 'description': 'Respond to user conversation. User message: What do you know about me?', 'agent': 'Friendly chatbot assistant'},
202198
run_id="my_run_id",
203199
user_id="test_user",
204200
agent_id='Test_Agent_Test_Agent_2_Test_Agent_3'
@@ -212,14 +208,14 @@ def test_save_method_with_memory_client(mem0_storage_with_memory_client_using_co
212208

213209
# Test short_term memory type (already set in fixture)
214210
test_value = "This is a test memory"
215-
test_metadata = {"key": "value"}
211+
test_metadata = {'description': 'Respond to user conversation. User message: What do you know about me?', 'messages': [{'role': 'system', 'content': 'You are Friendly chatbot assistant. You are a kind and knowledgeable chatbot assistant. You excel at understanding user needs, providing helpful responses, and maintaining engaging conversations. You remember previous interactions to provide a personalized experience.\nYour personal goal is: Engage in useful and interesting conversations with users while remembering context.\nTo give my best complete final answer to the task respond using the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!'}, {'role': 'user', 'content': '\nCurrent Task: Respond to user conversation. User message: What do you know about me?\n\nThis is the expected criteria for your final answer: Contextually appropriate, helpful, and friendly response.\nyou MUST return the actual complete content as the final answer, not a summary.\n\n# Useful context: \nExternal memories:\n- User is from India\n- User is interested in the solar system\n- User name is Vidit Ostwal\n- User is interested in French cuisine\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:'}, {'role': 'assistant', 'content': "I now can give a great answer \nFinal Answer: Hi Vidit! From our previous conversations, I know you're from India and have a great interest in the solar system. It's fascinating to explore the wonders of space, isn't it? Also, I remember you have a passion for French cuisine, which has so many delightful dishes to explore. If there's anything specific you'd like to discuss or learn about—whether it's about the solar system or some great French recipes—feel free to let me know! I'm here to help."}], 'agent': 'Friendly chatbot assistant'}
216212

217213
mem0_storage.save(test_value, test_metadata)
218214

219215
mem0_storage.memory.add.assert_called_once_with(
220-
[{'role': 'assistant' , 'content': test_value}],
216+
[{'role': 'user', 'content': 'What do you know about me?'}, {'role': 'assistant', 'content': "Hi Vidit! From our previous conversations, I know you're from India and have a great interest in the solar system. It's fascinating to explore the wonders of space, isn't it? Also, I remember you have a passion for French cuisine, which has so many delightful dishes to explore. If there's anything specific you'd like to discuss or learn about—whether it's about the solar system or some great French recipes—feel free to let me know! I'm here to help."}],
221217
infer=True,
222-
metadata={"type": "short_term", "key": "value"},
218+
metadata={'type': 'short_term', 'description': 'Respond to user conversation. User message: What do you know about me?', 'agent': 'Friendly chatbot assistant'},
223219
version="v2",
224220
run_id="my_run_id",
225221
includes="include1",

0 commit comments

Comments
 (0)