1- from unittest .mock import Mock , patch
1+ from unittest .mock import MagicMock , Mock , patch
22
33import pytest
44
5+ from mem0 import AsyncMemory , Memory
56from mem0 .configs .llms .base import BaseLlmConfig
67from mem0 .llms .vllm import VllmLLM
8+ from mem0 .memory .utils import remove_code_blocks
79
810
911@pytest .fixture
@@ -84,3 +86,132 @@ def test_generate_response_with_tools(mock_vllm_client):
8486 assert len (response ["tool_calls" ]) == 1
8587 assert response ["tool_calls" ][0 ]["name" ] == "add_memory"
8688 assert response ["tool_calls" ][0 ]["arguments" ] == {"data" : "Today is a sunny day." }
89+
90+
91+
92+ def test_remove_thinking_tags ():
93+ # with thinking tag
94+ assert remove_code_blocks ('<think>abc</think>{"facts":["test fact"]}' ) == '{"facts":["test fact"]}'
95+
96+ # thinking content with multiple lines
97+ assert remove_code_blocks ('<think>\n abc\n </think>\n {"facts":["test fact"]}' ) == '{"facts":["test fact"]}'
98+
99+ # more than one thinking tag(rare in practice)
100+ assert remove_code_blocks ('<think>A</think><think>B</think>{"facts":["test fact"]}' ) == '{"facts":["test fact"]}'
101+
102+ # no tag
103+ assert remove_code_blocks ('{"facts":["test fact"]}' ) == '{"facts":["test fact"]}'
104+
105+
106+
107+
108+ def create_mocked_memory ():
109+ """Create a fully mocked Memory instance for testing."""
110+ with patch ('mem0.utils.factory.LlmFactory.create' ) as mock_llm_factory , \
111+ patch ('mem0.utils.factory.EmbedderFactory.create' ) as mock_embedder_factory , \
112+ patch ('mem0.utils.factory.VectorStoreFactory.create' ) as mock_vector_factory , \
113+ patch ('mem0.memory.storage.SQLiteManager' ) as mock_sqlite :
114+
115+ mock_llm = MagicMock ()
116+ mock_llm_factory .return_value = mock_llm
117+
118+ mock_embedder = MagicMock ()
119+ mock_embedder .embed .return_value = [0.1 , 0.2 , 0.3 ]
120+ mock_embedder_factory .return_value = mock_embedder
121+
122+ mock_vector_store = MagicMock ()
123+ mock_vector_store .search .return_value = []
124+ mock_vector_store .add .return_value = None
125+ mock_vector_factory .return_value = mock_vector_store
126+
127+ mock_sqlite .return_value = MagicMock ()
128+
129+ memory = Memory ()
130+ memory .api_version = "v1.0"
131+ return memory , mock_llm , mock_vector_store
132+
133+
134+ def create_mocked_async_memory ():
135+ """Create a fully mocked AsyncMemory instance for testing."""
136+ with patch ('mem0.utils.factory.LlmFactory.create' ) as mock_llm_factory , \
137+ patch ('mem0.utils.factory.EmbedderFactory.create' ) as mock_embedder_factory , \
138+ patch ('mem0.utils.factory.VectorStoreFactory.create' ) as mock_vector_factory , \
139+ patch ('mem0.memory.storage.SQLiteManager' ) as mock_sqlite :
140+
141+ mock_llm = MagicMock ()
142+ mock_llm_factory .return_value = mock_llm
143+
144+ mock_embedder = MagicMock ()
145+ mock_embedder .embed .return_value = [0.1 , 0.2 , 0.3 ]
146+ mock_embedder_factory .return_value = mock_embedder
147+
148+ mock_vector_store = MagicMock ()
149+ mock_vector_store .search .return_value = []
150+ mock_vector_store .add .return_value = None
151+ mock_vector_factory .return_value = mock_vector_store
152+
153+ mock_sqlite .return_value = MagicMock ()
154+
155+ memory = AsyncMemory ()
156+ memory .api_version = "v1.0"
157+ return memory , mock_llm , mock_vector_store
158+
159+
160+ def test_thinking_tags_in_add_to_vector_store ():
161+ """Test thinking tags handling in Memory._add_to_vector_store (sync)."""
162+ memory , mock_llm , mock_vector_store = create_mocked_memory ()
163+
164+ # Mock LLM responses for both phases
165+ mock_llm .generate_response .side_effect = [
166+ ' <think>Sync fact extraction</think> \n {"facts": ["User loves sci-fi"]}' ,
167+ ' <think>Sync memory actions</think> \n {"memory": [{"text": "Loves sci-fi", "event": "ADD"}]}'
168+ ]
169+
170+ mock_vector_store .search .return_value = []
171+
172+ result = memory ._add_to_vector_store (
173+ messages = [{"role" : "user" , "content" : "I love sci-fi movies" }],
174+ metadata = {},
175+ filters = {},
176+ infer = True
177+ )
178+
179+ assert len (result ) == 1
180+ assert result [0 ]["memory" ] == "Loves sci-fi"
181+ assert result [0 ]["event" ] == "ADD"
182+
183+
184+
185+ @pytest .mark .asyncio
186+ async def test_async_thinking_tags_in_add_to_vector_store ():
187+ """Test thinking tags handling in AsyncMemory._add_to_vector_store."""
188+ memory , mock_llm , mock_vector_store = create_mocked_async_memory ()
189+
190+ # Directly mock llm.generate_response instead of via asyncio.to_thread
191+ mock_llm .generate_response .side_effect = [
192+ ' <think>Async fact extraction</think> \n {"facts": ["User loves sci-fi"]}' ,
193+ ' <think>Async memory actions</think> \n {"memory": [{"text": "Loves sci-fi", "event": "ADD"}]}'
194+ ]
195+
196+ # Mock asyncio.to_thread to call the function directly (bypass threading)
197+ async def mock_to_thread (func , * args , ** kwargs ):
198+ if func == mock_llm .generate_response :
199+ return func (* args , ** kwargs )
200+ elif hasattr (func , '__name__' ) and 'embed' in func .__name__ :
201+ return [0.1 , 0.2 , 0.3 ]
202+ elif hasattr (func , '__name__' ) and 'search' in func .__name__ :
203+ return []
204+ else :
205+ return func (* args , ** kwargs )
206+
207+ with patch ('mem0.memory.main.asyncio.to_thread' , side_effect = mock_to_thread ):
208+ result = await memory ._add_to_vector_store (
209+ messages = [{"role" : "user" , "content" : "I love sci-fi movies" }],
210+ metadata = {},
211+ effective_filters = {},
212+ infer = True
213+ )
214+
215+ assert len (result ) == 1
216+ assert result [0 ]["memory" ] == "Loves sci-fi"
217+ assert result [0 ]["event" ] == "ADD"
0 commit comments