From 84687fc3d2168dcdeff3fdba54fb358cd64aca9d Mon Sep 17 00:00:00 2001 From: Parth Sharma <109902593+parthshr370@users.noreply.github.com> Date: Fri, 31 Oct 2025 16:46:10 +0530 Subject: [PATCH] [fix] list' object has no attribute 'id' - Id fault with chroma pinecone and other providers (#3693) --- mem0/memory/main.py | 36 ++++++++++---- tests/test_memory.py | 110 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 134 insertions(+), 12 deletions(-) diff --git a/mem0/memory/main.py b/mem0/memory/main.py index db19a48be..99bc0b1e5 100644 --- a/mem0/memory/main.py +++ b/mem0/memory/main.py @@ -710,11 +710,19 @@ class Memory(MemoryBase): def _get_all_from_vector_store(self, filters, limit): memories_result = self.vector_store.list(filters=filters, limit=limit) - actual_memories = ( - memories_result[0] - if isinstance(memories_result, (tuple)) and len(memories_result) > 0 - else memories_result - ) + + # Handle different vector store return formats by inspecting first element + if isinstance(memories_result, (tuple, list)) and len(memories_result) > 0: + first_element = memories_result[0] + + # If first element is a container, unwrap one level + if isinstance(first_element, (list, tuple)): + actual_memories = first_element + else: + # First element is a memory object, structure is already flat + actual_memories = memories_result + else: + actual_memories = memories_result promoted_payload_keys = [ "user_id", @@ -1751,11 +1759,19 @@ class AsyncMemory(MemoryBase): async def _get_all_from_vector_store(self, filters, limit): memories_result = await asyncio.to_thread(self.vector_store.list, filters=filters, limit=limit) - actual_memories = ( - memories_result[0] - if isinstance(memories_result, (tuple)) and len(memories_result) > 0 - else memories_result - ) + + # Handle different vector store return formats by inspecting first element + if isinstance(memories_result, (tuple, list)) and len(memories_result) > 0: + first_element = memories_result[0] + + # If first element is a container, unwrap one level + if isinstance(first_element, (list, tuple)): + actual_memories = first_element + else: + # First element is a memory object, structure is already flat + actual_memories = memories_result + else: + actual_memories = memories_result promoted_payload_keys = [ "user_id", diff --git a/tests/test_memory.py b/tests/test_memory.py index f5c3d3bac..39af062ef 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -137,5 +137,111 @@ def test_search_handles_incomplete_payloads(mock_sqlite, mock_llm_factory, mock_ assert len(result) == 2 memories_by_id = {mem["id"]: mem for mem in result} - assert memories_by_id["mem_1"]["memory"] == "" - assert memories_by_id["mem_2"]["memory"] == "content" + assert memories_by_id["mem_1"]["memory"] == "" + assert memories_by_id["mem_2"]["memory"] == "content" + + +@patch('mem0.utils.factory.EmbedderFactory.create') +@patch('mem0.utils.factory.VectorStoreFactory.create') +@patch('mem0.utils.factory.LlmFactory.create') +@patch('mem0.memory.storage.SQLiteManager') +def test_get_all_handles_nested_list_from_chroma(mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory): + """ + Test that get_all() handles nested list return from Chroma/Milvus. + + Issue #3674: Some vector stores return [[mem1, mem2]] instead of [mem1, mem2] + This test ensures the unified unwrapping logic handles this correctly. + """ + 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 Memory as MemoryClass + config = MemoryConfig() + memory = MemoryClass(config) + + # Create test data + mem1 = MockVectorMemory("mem_1", {"data": "My dog name is Sheru"}) + mem2 = MockVectorMemory("mem_2", {"data": "I like to code in Python"}) + mem3 = MockVectorMemory("mem_3", {"data": "I live in California"}) + + # Chroma/Milvus returns nested list: [[mem1, mem2, mem3]] + mock_vector_store.list.return_value = [[mem1, mem2, mem3]] + + result = memory._get_all_from_vector_store({"user_id": "test"}, 100) + + # Should successfully unwrap and return 3 memories + assert len(result) == 3 + assert result[0]["memory"] == "My dog name is Sheru" + assert result[1]["memory"] == "I like to code in Python" + assert result[2]["memory"] == "I live in California" + + +@patch('mem0.utils.factory.EmbedderFactory.create') +@patch('mem0.utils.factory.VectorStoreFactory.create') +@patch('mem0.utils.factory.LlmFactory.create') +@patch('mem0.memory.storage.SQLiteManager') +def test_get_all_handles_tuple_from_qdrant(mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory): + """ + Test that get_all() handles tuple return from Qdrant. + + Qdrant returns: ([mem1, mem2], count) + Should unwrap to [mem1, mem2] + """ + 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 Memory as MemoryClass + config = MemoryConfig() + memory = MemoryClass(config) + + mem1 = MockVectorMemory("mem_1", {"data": "Memory 1"}) + mem2 = MockVectorMemory("mem_2", {"data": "Memory 2"}) + + # Qdrant returns tuple: ([mem1, mem2], count) + mock_vector_store.list.return_value = ([mem1, mem2], 100) + + result = memory._get_all_from_vector_store({"user_id": "test"}, 100) + + assert len(result) == 2 + assert result[0]["memory"] == "Memory 1" + assert result[1]["memory"] == "Memory 2" + + +@patch('mem0.utils.factory.EmbedderFactory.create') +@patch('mem0.utils.factory.VectorStoreFactory.create') +@patch('mem0.utils.factory.LlmFactory.create') +@patch('mem0.memory.storage.SQLiteManager') +def test_get_all_handles_flat_list_from_postgres(mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory): + """ + Test that get_all() handles flat list return from PostgreSQL. + + PostgreSQL returns: [mem1, mem2] + Should keep as-is without unwrapping + """ + 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 Memory as MemoryClass + config = MemoryConfig() + memory = MemoryClass(config) + + mem1 = MockVectorMemory("mem_1", {"data": "Memory 1"}) + mem2 = MockVectorMemory("mem_2", {"data": "Memory 2"}) + + # PostgreSQL returns flat list: [mem1, mem2] + mock_vector_store.list.return_value = [mem1, mem2] + + result = memory._get_all_from_vector_store({"user_id": "test"}, 100) + + assert len(result) == 2 + assert result[0]["memory"] == "Memory 1" + assert result[1]["memory"] == "Memory 2"