Compare commits

...

2 Commits

Author SHA1 Message Date
kartik-mem0 6b2850c30f Merge branch 'main' into fix/client-host-env
# Conflicts:
#	tests/test_client.py
2026-08-14 17:17:28 +05:30
kartik-mem0 d371c1d931 fix(client): honor MEM0_HOST and MEM0_API_URL environment variables
MemoryClient and AsyncMemoryClient hardcoded the default host, so a
self-hosted or proxied deployment had to pass host= at every construction
site even with MEM0_HOST exported. The API key already reads from the
environment; the host now does too.

Resolution order: explicit host argument, MEM0_HOST, MEM0_API_URL, then
https://api.mem0.ai. Existing callers are unaffected.

Closes #6622
2026-08-13 17:31:23 +05:30
2 changed files with 89 additions and 7 deletions
+15 -6
View File
@@ -39,6 +39,13 @@ setup_config()
# Entity parameters that must be passed via filters, not top-level
ENTITY_PARAMS = frozenset({"user_id", "agent_id", "app_id", "run_id"})
DEFAULT_HOST = "https://api.mem0.ai"
def _resolve_host(host: Optional[str]) -> str:
"""Resolve the API host from the explicit argument, then MEM0_HOST, then MEM0_API_URL."""
return host or os.getenv("MEM0_HOST") or os.getenv("MEM0_API_URL") or DEFAULT_HOST
def _validate_and_trim_search_query(query: str) -> str:
if not isinstance(query, str):
@@ -104,8 +111,9 @@ class MemoryClient:
api_key: The API key for authenticating with the Mem0 API. If not
provided, it will attempt to use the MEM0_API_KEY
environment variable.
host: The base URL for the Mem0 API. Defaults to
"https://api.mem0.ai".
host: The base URL for the Mem0 API. If not provided, it will
attempt to use the MEM0_HOST or MEM0_API_URL environment
variables, then default to "https://api.mem0.ai".
client: A custom httpx.Client instance. If provided, it will be
used instead of creating a new one. Note that base_url and
headers will be set/overridden as needed.
@@ -114,7 +122,7 @@ class MemoryClient:
ValueError: If no API key is provided or found in the environment.
"""
self.api_key = api_key or os.getenv("MEM0_API_KEY")
self.host = host or "https://api.mem0.ai"
self.host = _resolve_host(host)
self.org_id = None
self.project_id = None
self.user_id = get_user_id()
@@ -993,8 +1001,9 @@ class AsyncMemoryClient:
api_key: The API key for authenticating with the Mem0 API. If not
provided, it will attempt to use the MEM0_API_KEY
environment variable.
host: The base URL for the Mem0 API. Defaults to
"https://api.mem0.ai".
host: The base URL for the Mem0 API. If not provided, it will
attempt to use the MEM0_HOST or MEM0_API_URL environment
variables, then default to "https://api.mem0.ai".
client: A custom httpx.AsyncClient instance. If provided, it will
be used instead of creating a new one. Note that base_url
and headers will be set/overridden as needed.
@@ -1003,7 +1012,7 @@ class AsyncMemoryClient:
ValueError: If no API key is provided or found in the environment.
"""
self.api_key = api_key or os.getenv("MEM0_API_KEY")
self.host = host or "https://api.mem0.ai"
self.host = _resolve_host(host)
self.org_id = None
self.project_id = None
self.user_id = get_user_id()
+74 -1
View File
@@ -8,7 +8,7 @@ import httpx
import pytest
import requests
from mem0.client.main import AsyncMemoryClient
from mem0.client.main import DEFAULT_HOST, AsyncMemoryClient, _resolve_host
from mem0.client.types import GetAllMemoryOptions, SearchMemoryOptions
@@ -633,3 +633,76 @@ class TestAddAgentCustomInstructions:
_, kwargs = mock_memory_client.client.post.call_args
assert "agent_custom_instructions" not in kwargs["json"]
class TestHostResolution:
"""host comes from the explicit argument, then MEM0_HOST, then MEM0_API_URL, then the default."""
@pytest.fixture(autouse=True)
def _clear_host_env(self, monkeypatch):
monkeypatch.delenv("MEM0_HOST", raising=False)
monkeypatch.delenv("MEM0_API_URL", raising=False)
def test_explicit_argument_wins_over_env(self, monkeypatch):
monkeypatch.setenv("MEM0_HOST", "https://host-env.example.com")
monkeypatch.setenv("MEM0_API_URL", "https://url-env.example.com")
assert _resolve_host("https://explicit.example.com") == "https://explicit.example.com"
def test_mem0_host_wins_over_mem0_api_url(self, monkeypatch):
monkeypatch.setenv("MEM0_HOST", "https://host-env.example.com")
monkeypatch.setenv("MEM0_API_URL", "https://url-env.example.com")
assert _resolve_host(None) == "https://host-env.example.com"
def test_mem0_api_url_is_used_when_mem0_host_is_unset(self, monkeypatch):
monkeypatch.setenv("MEM0_API_URL", "https://url-env.example.com")
assert _resolve_host(None) == "https://url-env.example.com"
def test_empty_env_value_falls_through(self, monkeypatch):
monkeypatch.setenv("MEM0_HOST", "")
monkeypatch.setenv("MEM0_API_URL", "https://url-env.example.com")
assert _resolve_host(None) == "https://url-env.example.com"
def test_defaults_when_nothing_is_set(self):
assert _resolve_host(None) == DEFAULT_HOST == "https://api.mem0.ai"
def test_sync_client_uses_mem0_host(self, monkeypatch):
monkeypatch.setenv("MEM0_HOST", "https://host-env.example.com")
with patch("mem0.client.main.httpx.Client") as mock_httpx, patch("mem0.client.main.capture_client_event"):
mock_httpx.return_value = MagicMock(
get=MagicMock(
return_value=MagicMock(
json=lambda: {"org_id": "org1", "project_id": "proj1", "user_email": "test@test.com"},
raise_for_status=lambda: None,
)
)
)
from mem0.client.main import MemoryClient
client = MemoryClient(api_key="test-api-key")
assert client.host == "https://host-env.example.com"
assert mock_httpx.call_args.kwargs["base_url"] == "https://host-env.example.com"
def test_async_client_uses_mem0_api_url(self, monkeypatch):
monkeypatch.setenv("MEM0_API_URL", "https://url-env.example.com")
with (
patch("mem0.client.main.httpx.AsyncClient"),
patch("mem0.client.main.capture_client_event"),
patch("mem0.client.main.requests.get") as mock_get,
):
mock_get.return_value = MagicMock(
json=lambda: {"org_id": "org1", "project_id": "proj1", "user_email": "test@test.com"},
raise_for_status=lambda: None,
)
from mem0.client.main import AsyncMemoryClient
client = AsyncMemoryClient(api_key="test-api-key")
assert client.host == "https://url-env.example.com"
assert mock_get.call_args.args[0] == "https://url-env.example.com/v1/ping/"