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
11 changes: 10 additions & 1 deletion src/strands_tools/mem0_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@
import boto3
from mem0 import Memory as Mem0Memory
from mem0 import MemoryClient
from opensearchpy import AWSV4SignerAuth, RequestsHttpConnection
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
Expand Down Expand Up @@ -332,6 +331,16 @@ def _append_opensearch_config(self, config: Optional[Dict] = None) -> Dict:
Returns:
An initialized Mem0Memory instance configured for OpenSearch.
"""
try:
from opensearchpy import AWSV4SignerAuth, RequestsHttpConnection
except ModuleNotFoundError as error:
if error.name != "opensearchpy":
raise
raise ImportError(
"The opensearch-py package is required for the OpenSearch backend. "
"Install it with: pip install 'strands-agents-tools[mem0-memory]'"
) from error

# Add vector portion of the config
config = config or {}
config["vector_store"] = {
Expand Down
42 changes: 42 additions & 0 deletions tests/test_mem0.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import builtins
import json
import os
import subprocess
import sys
import textwrap
from unittest.mock import MagicMock, patch

import pytest
Expand Down Expand Up @@ -487,6 +489,46 @@ def fail_faiss(name, *args, **kwargs):
assert "The faiss-cpu package is required" in str(result["content"][0]["text"])


def test_module_import_does_not_require_opensearch():
"""FAISS users can import mem0_memory without the OpenSearch dependency."""
script = textwrap.dedent(
"""
import builtins

real_import = builtins.__import__

def block_opensearch(name, *args, **kwargs):
if name == "opensearchpy" or name.startswith("opensearchpy."):
raise ModuleNotFoundError("No module named 'opensearchpy'", name="opensearchpy")
return real_import(name, *args, **kwargs)

builtins.__import__ = block_opensearch
from strands_tools.mem0_memory import Mem0ServiceClient

assert Mem0ServiceClient is not None
"""
)

result = subprocess.run([sys.executable, "-c", script], capture_output=True, text=True, check=False)

assert result.returncode == 0, result.stderr


def test_opensearch_backend_reports_missing_optional_dependency():
"""The OpenSearch path explains how to install its optional dependency."""
client = object.__new__(Mem0ServiceClient)
real_import = builtins.__import__

def block_opensearch(name, *args, **kwargs):
if name == "opensearchpy" or name.startswith("opensearchpy."):
raise ModuleNotFoundError("No module named 'opensearchpy'", name="opensearchpy")
return real_import(name, *args, **kwargs)

with patch("builtins.__import__", side_effect=block_opensearch):
with pytest.raises(ImportError, match="opensearch-py.*mem0-memory"):
client._append_opensearch_config()


@patch("boto3.Session")
@patch("strands_tools.mem0_memory.Mem0Memory")
@patch("opensearchpy.OpenSearch")
Expand Down
Loading