Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions mem0/memory/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,8 @@ def _add_to_vector_store(self, messages, metadata, filters, infer):

msg_content = message_dict["content"]
msg_embeddings = self.embedding_model.embed(msg_content, "add")
mem_id = self._create_memory(msg_content, msg_embeddings, per_msg_meta)
# Pass embeddings as a mapping so _create_memory reuses the computed vector
mem_id = self._create_memory(msg_content, {msg_content: msg_embeddings}, per_msg_meta)

returned_memories.append(
{
Expand Down Expand Up @@ -1074,8 +1075,11 @@ def history(self, memory_id):

def _create_memory(self, data, existing_embeddings, metadata=None):
logger.debug(f"Creating memory with {data=}")
if data in existing_embeddings:
# existing_embeddings may be a mapping (preferred) or a precomputed vector
if isinstance(existing_embeddings, dict) and data in existing_embeddings:
embeddings = existing_embeddings[data]
elif existing_embeddings is not None and not isinstance(existing_embeddings, dict):
embeddings = existing_embeddings
else:
embeddings = self.embedding_model.embed(data, memory_action="add")
memory_id = str(uuid.uuid4())
Expand Down Expand Up @@ -1437,7 +1441,8 @@ async def _add_to_vector_store(

msg_content = message_dict["content"]
msg_embeddings = await asyncio.to_thread(self.embedding_model.embed, msg_content, "add")
mem_id = await self._create_memory(msg_content, msg_embeddings, per_msg_meta)
# Pass embeddings as a mapping so _create_memory reuses the computed vector
mem_id = await self._create_memory(msg_content, {msg_content: msg_embeddings}, per_msg_meta)

returned_memories.append(
{
Expand Down Expand Up @@ -2135,8 +2140,11 @@ async def history(self, memory_id):

async def _create_memory(self, data, existing_embeddings, metadata=None):
logger.debug(f"Creating memory with {data=}")
if data in existing_embeddings:
# existing_embeddings may be a mapping (preferred) or a precomputed vector
if isinstance(existing_embeddings, dict) and data in existing_embeddings:
embeddings = existing_embeddings[data]
elif existing_embeddings is not None and not isinstance(existing_embeddings, dict):
embeddings = existing_embeddings
else:
embeddings = await asyncio.to_thread(self.embedding_model.embed, data, memory_action="add")

Expand Down
3 changes: 2 additions & 1 deletion mem0/vector_stores/azure_ai_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ def search(self, query, vectors, limit=5, filters=None):
if filters:
filter_expression = self._build_filter_expression(filters)

vector_query = VectorizedQuery(vector=vectors, k_nearest_neighbors=limit, fields="vector")
# Azure VectorizedQuery uses `k` for nearest neighbors in current SDKs.
vector_query = VectorizedQuery(vector=vectors, k=limit, fields="vector")
if self.hybrid_search:
search_results = self.search_client.search(
search_text=query,
Expand Down
32 changes: 32 additions & 0 deletions tests/test_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,35 @@ def test_get_all_handles_flat_list_from_postgres(mock_sqlite, mock_llm_factory,
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_add_infer_false_embeds_once(mock_sqlite, mock_llm_factory, mock_vector_factory, mock_embedder_factory):
"""
Regression test: adding with infer=False should not trigger duplicate embedding calls.
"""
embedder = MagicMock()
embedder.embed.return_value = [0.1, 0.2, 0.3]
embedder.config = MagicMock(embedding_dims=3)
mock_embedder_factory.return_value = embedder

mock_vector_store = MagicMock()
mock_vector_store.search.return_value = []
mock_vector_store.insert.return_value = None
mock_vector_store.get.return_value = None
telemetry_vector_store = MagicMock()
mock_vector_factory.side_effect = [mock_vector_store, telemetry_vector_store]

mock_llm_factory.return_value = MagicMock()
mock_sqlite.return_value = MagicMock()

from mem0.memory.main import Memory as MemoryClass
memory = MemoryClass(MemoryConfig())

memory.add("foo", user_id="test_user", infer=False)

assert embedder.embed.call_count == 1
mock_vector_store.insert.assert_called_once()
2 changes: 1 addition & 1 deletion tests/vector_stores/test_azure_ai_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ def test_search_basic(azure_ai_search_instance):
# Check parameters
assert len(kwargs["vector_queries"]) == 1
assert kwargs["vector_queries"][0].vector == query_vector
assert kwargs["vector_queries"][0].k_nearest_neighbors == 5
assert kwargs["vector_queries"][0].k == 5
assert kwargs["vector_queries"][0].fields == "vector"
assert kwargs["filter"] is None # No filters
assert kwargs["top"] == 5
Expand Down