Compare commits

..
Author SHA1 Message Date
kartik-mem0 83f2d23ddb chore(tests): fix Gemini embeddings test filename, add Langchain embedder tests
tests/embeddings/test_gemini_emeddings.py has a typo in its name. #6095
reported it as a duplicate of test_gemini_embeddings.py, but the
correctly-spelled file has never existed in this repo, so the typo file is
the only Gemini embeddings coverage there is. Renaming keeps all 119 lines;
deleting it would drop the suite.

mem0/embeddings/langchain.py had no tests at all. Adds five covering the
model-instance validation, the missing-model guard, and embed() delegating
to embed_query, mirroring tests/llms/test_langchain.py including its
importorskip guard.

Closes #6095
2026-08-13 17:41:13 +05:30
5 changed files with 65 additions and 63 deletions
+2 -8
View File
@@ -1,7 +1,7 @@
import hashlib
import logging
import re
from typing import Any, Dict, List, Union
from typing import Any, Dict, List
from mem0.configs.prompts import (
AGENT_MEMORY_EXTRACTION_PROMPT,
@@ -112,7 +112,7 @@ def normalize_facts(raw_facts):
return normalized
def remove_code_blocks(content: Union[str, List[Union[str, Dict[str, Any]]], None]) -> str:
def remove_code_blocks(content: str) -> str:
"""
Removes enclosing code block markers ```[language] and ``` from a given string.
@@ -120,15 +120,9 @@ def remove_code_blocks(content: Union[str, List[Union[str, Dict[str, Any]]], Non
- The function uses a regex pattern to match code blocks that may start with ``` followed by an optional language tag (letters or numbers) and end with ```.
- If a code block is detected, it returns only the inner content, stripping out the markers.
- If no code block markers are found, the original content is returned as-is.
- Block-style content (a LangChain message whose `.content` is a list of text
blocks) is flattened to its concatenated text before matching.
"""
if content is None:
return ""
if isinstance(content, list):
content = "".join(
part if isinstance(part, str) else part.get("text", "") for part in content if isinstance(part, (str, dict))
)
pattern = r"^```[a-zA-Z0-9]*\n([\s\S]*?)\n```$"
match = re.match(pattern, content.strip())
match_res=match.group(1).strip() if match else content.strip()
@@ -0,0 +1,61 @@
from unittest.mock import Mock
import pytest
pytest.importorskip("langchain", reason="langchain not installed")
from mem0.configs.embeddings.base import BaseEmbedderConfig
from mem0.embeddings.langchain import LangchainEmbedding
try:
from langchain.embeddings.base import Embeddings
except ImportError:
from unittest.mock import MagicMock
Embeddings = MagicMock
@pytest.fixture
def mock_langchain_model():
"""Mock a Langchain embeddings model for testing."""
mock_model = Mock(spec=Embeddings)
mock_model.embed_query.return_value = [0.1, 0.2, 0.3]
return mock_model
def test_langchain_initialization(mock_langchain_model):
"""LangchainEmbedding keeps the configured model instance."""
embedder = LangchainEmbedding(BaseEmbedderConfig(model=mock_langchain_model))
assert embedder.langchain_model is mock_langchain_model
def test_embed_delegates_to_embed_query(mock_langchain_model):
"""embed() forwards the text to the model's embed_query and returns its vector."""
embedder = LangchainEmbedding(BaseEmbedderConfig(model=mock_langchain_model))
embedding = embedder.embed("Sample text to embed.")
mock_langchain_model.embed_query.assert_called_once_with("Sample text to embed.")
assert embedding == [0.1, 0.2, 0.3]
def test_embed_ignores_memory_action(mock_langchain_model):
"""memory_action is accepted for interface parity and never reaches the model."""
embedder = LangchainEmbedding(BaseEmbedderConfig(model=mock_langchain_model))
embedder.embed("Sample text to embed.", memory_action="add")
mock_langchain_model.embed_query.assert_called_once_with("Sample text to embed.")
def test_invalid_model():
"""A model that is not an Embeddings instance is rejected."""
with pytest.raises(ValueError, match="`model` must be an instance of Embeddings"):
LangchainEmbedding(BaseEmbedderConfig(model="not-a-valid-model-instance"))
def test_missing_model():
"""A missing model is rejected before any embedding call."""
with pytest.raises(ValueError, match="`model` parameter is required"):
LangchainEmbedding(BaseEmbedderConfig(model=None))
-18
View File
@@ -192,21 +192,3 @@ class TestProcessTelemetryFilters:
class TestRemoveCodeBlocks:
def test_none_content_returns_empty_string(self):
assert remove_code_blocks(None) == ""
def test_block_list_content_is_flattened(self):
assert remove_code_blocks([{"type": "text", "text": "step one"}]) == "step one"
def test_block_list_content_strips_code_fences(self):
content = [{"type": "text", "text": '```json\n{"a": 1}\n```'}]
assert remove_code_blocks(content) == '{"a": 1}'
def test_block_list_content_joins_multiple_blocks(self):
content = [{"type": "text", "text": "step one. "}, {"type": "text", "text": "step two"}]
assert remove_code_blocks(content) == "step one. step two"
def test_block_list_content_ignores_non_text_blocks(self):
content = [{"type": "thinking", "thinking": "hmm"}, {"type": "text", "text": "answer"}]
assert remove_code_blocks(content) == "answer"
def test_plain_string_blocks_are_supported(self):
assert remove_code_blocks(["step one. ", "step two"]) == "step one. step two"
+2 -37
View File
@@ -1235,9 +1235,8 @@ class TestHybridSearchWarning:
def test_warning_for_store_without_keyword_search(
self, mock_vs_factory, mock_emb, mock_llm, mock_sqlite, _cap, caplog
):
import logging
from mem0.vector_stores.base import VectorStoreBase
import logging
class StoreWithoutKeywordSearch(VectorStoreBase):
def create_col(self, *a, **kw): pass
@@ -1272,9 +1271,8 @@ class TestHybridSearchWarning:
def test_no_warning_for_store_with_keyword_search(
self, mock_vs_factory, mock_emb, mock_llm, mock_sqlite, _cap, caplog
):
import logging
from mem0.vector_stores.base import VectorStoreBase
import logging
class StoreWithKeywordSearch(VectorStoreBase):
def keyword_search(self, query, top_k=5, filters=None):
@@ -1538,39 +1536,6 @@ async def test_async_procedural_memory_langchain_strips_code_blocks(mock_llm_fac
assert "```" not in stored_data
@pytest.mark.asyncio
@patch("mem0.memory.main.VectorStoreFactory")
@patch("mem0.memory.main.EmbedderFactory")
@patch("mem0.memory.main.LlmFactory")
async def test_async_procedural_memory_langchain_block_list_content(mock_llm_factory, mock_emb, mock_vs):
"""Regression #6150: block-list message content must not raise AttributeError."""
mock_vs.return_value = MagicMock()
mock_emb.return_value = MagicMock()
mock_emb.return_value.embed.return_value = [0.1] * 1536
mock_llm_factory.return_value = MagicMock()
from mem0.memory.main import AsyncMemory
config = MemoryConfig()
memory = AsyncMemory(config)
memory.vector_store = MagicMock()
memory.vector_store.insert = MagicMock()
mock_langchain_llm = MagicMock()
mock_response = MagicMock()
mock_response.content = [{"type": "text", "text": '```json\n{"key": "value"}\n```'}]
mock_langchain_llm.invoke.return_value = mock_response
messages = [{"role": "user", "content": "test"}]
metadata = {"user_id": "test_user"}
await memory._create_procedural_memory(messages, metadata=metadata, llm=mock_langchain_llm)
insert_call = memory.vector_store.insert.call_args
stored_data = insert_call[1]["payloads"][0]["data"]
assert stored_data == '{"key": "value"}'
@pytest.mark.asyncio
@patch("mem0.memory.main.VectorStoreFactory")
@patch("mem0.memory.main.EmbedderFactory")