[fix] list' object has no attribute 'id' - Id fault with chroma pinecone and other providers (#3693)
This commit is contained in:
+26
-10
@@ -710,11 +710,19 @@ class Memory(MemoryBase):
|
|||||||
|
|
||||||
def _get_all_from_vector_store(self, filters, limit):
|
def _get_all_from_vector_store(self, filters, limit):
|
||||||
memories_result = self.vector_store.list(filters=filters, limit=limit)
|
memories_result = self.vector_store.list(filters=filters, limit=limit)
|
||||||
actual_memories = (
|
|
||||||
memories_result[0]
|
# Handle different vector store return formats by inspecting first element
|
||||||
if isinstance(memories_result, (tuple)) and len(memories_result) > 0
|
if isinstance(memories_result, (tuple, list)) and len(memories_result) > 0:
|
||||||
else memories_result
|
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 = [
|
promoted_payload_keys = [
|
||||||
"user_id",
|
"user_id",
|
||||||
@@ -1751,11 +1759,19 @@ class AsyncMemory(MemoryBase):
|
|||||||
|
|
||||||
async def _get_all_from_vector_store(self, filters, limit):
|
async def _get_all_from_vector_store(self, filters, limit):
|
||||||
memories_result = await asyncio.to_thread(self.vector_store.list, filters=filters, limit=limit)
|
memories_result = await asyncio.to_thread(self.vector_store.list, filters=filters, limit=limit)
|
||||||
actual_memories = (
|
|
||||||
memories_result[0]
|
# Handle different vector store return formats by inspecting first element
|
||||||
if isinstance(memories_result, (tuple)) and len(memories_result) > 0
|
if isinstance(memories_result, (tuple, list)) and len(memories_result) > 0:
|
||||||
else memories_result
|
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 = [
|
promoted_payload_keys = [
|
||||||
"user_id",
|
"user_id",
|
||||||
|
|||||||
+108
-2
@@ -137,5 +137,111 @@ def test_search_handles_incomplete_payloads(mock_sqlite, mock_llm_factory, mock_
|
|||||||
assert len(result) == 2
|
assert len(result) == 2
|
||||||
memories_by_id = {mem["id"]: mem for mem in result}
|
memories_by_id = {mem["id"]: mem for mem in result}
|
||||||
|
|
||||||
assert memories_by_id["mem_1"]["memory"] == ""
|
assert memories_by_id["mem_1"]["memory"] == ""
|
||||||
assert memories_by_id["mem_2"]["memory"] == "content"
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user