diff --git a/mem0/memory/main.py b/mem0/memory/main.py index f3dc9051d..5295e6e39 100644 --- a/mem0/memory/main.py +++ b/mem0/memory/main.py @@ -3235,9 +3235,15 @@ class AsyncMemory(MemoryBase): for memory in memories[0]: delete_tasks.append(self._delete_memory(memory.id)) - await asyncio.gather(*delete_tasks) + results = await asyncio.gather(*delete_tasks, return_exceptions=True) - logger.info(f"Deleted {len(memories[0])} memories") + errors = [r for r in results if isinstance(r, BaseException)] + if errors: + logger.warning("Failed to delete %d out of %d memories", len(errors), len(results)) + for err in errors: + logger.warning("Delete error: %s", err) + + logger.info(f"Deleted {len(results) - len(errors)} memories") decay_usage_notice = detect_decay_usage_from_delete_all(len(memories[0])) if decay_usage_notice: diff --git a/tests/test_memory.py b/tests/test_memory.py index 423425332..7ef17c81c 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -762,6 +762,55 @@ async def test_async_delete_memory_history_has_timestamps(mock_sqlite, mock_llm_ datetime.fromisoformat(call_kwargs["updated_at"]) # verify valid ISO timestamp +@pytest.mark.asyncio +@patch('mem0.utils.factory.EmbedderFactory.create') +@patch('mem0.utils.factory.VectorStoreFactory.create') +@patch('mem0.utils.factory.LlmFactory.create') +@patch('mem0.memory.main.SQLiteManager') +async def test_async_delete_all_continues_on_partial_failure(mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory): + """async delete_all must not abort when a single memory fails to delete. + + Without return_exceptions=True, asyncio.gather raises on the first error + and cancels remaining tasks, leaving a partial deletion. + """ + mock_embedder_factory.return_value = MagicMock() + mock_vector_store = MagicMock() + mock_vector_factory.return_value = mock_vector_store + mock_llm_factory.return_value = MagicMock() + mock_sqlite.return_value = MagicMock() + + from mem0.memory.main import AsyncMemory + config = MemoryConfig() + memory = AsyncMemory(config) + + mem1 = MagicMock() + mem1.id = "mem-1" + mem1.payload = {"data": "one", "created_at": "2024-01-01T00:00:00+00:00", "actor_id": None, "role": None} + mem2 = MagicMock() + mem2.id = "mem-2" + mem2.payload = {"data": "two", "created_at": "2024-01-01T00:00:00+00:00", "actor_id": None, "role": None} + mem3 = MagicMock() + mem3.id = "mem-3" + mem3.payload = {"data": "three", "created_at": "2024-01-01T00:00:00+00:00", "actor_id": None, "role": None} + + mock_vector_store.list.return_value = ([mem1, mem2, mem3],) + + def _get_side_effect(vector_id): + if vector_id == "mem-2": + raise RuntimeError("simulated store failure") + return { + "mem-1": mem1, + "mem-3": mem3, + }.get(vector_id) + + mock_vector_store.get.side_effect = _get_side_effect + + result = await memory.delete_all(user_id="test-user") + + assert result == {"message": "Memories deleted successfully!"} + assert mock_vector_store.delete.call_count == 2 + + @patch('mem0.utils.factory.EmbedderFactory.create') @patch('mem0.utils.factory.VectorStoreFactory.create') @patch('mem0.utils.factory.LlmFactory.create')