From 07f07c7b51643fe2ebbd6ac582f80b2a1b1c29d2 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Fri, 17 Jul 2026 05:42:43 -0700 Subject: [PATCH] fix(mem0): migrate legacy OSS base URL aliases Normalize stale api_base keys to each mem0 provider's accepted URL field before Memory.from_config, without mutating the saved config. --- plugins/memory/mem0/_backend.py | 21 ++++++++++-- plugins/memory/mem0/_oss_providers.py | 4 +++ plugins/memory/mem0/_setup.py | 10 +++--- tests/plugins/memory/test_mem0_backend.py | 40 +++++++++++++++++++++++ tests/plugins/memory/test_mem0_setup.py | 17 ++++++++++ 5 files changed, 86 insertions(+), 6 deletions(-) diff --git a/plugins/memory/mem0/_backend.py b/plugins/memory/mem0/_backend.py index 7292fd766..014417fac 100644 --- a/plugins/memory/mem0/_backend.py +++ b/plugins/memory/mem0/_backend.py @@ -160,6 +160,23 @@ class OSSBackend(Mem0Backend): import os from mem0 import Memory + def _provider_block(name: str) -> dict: + block = dict(oss_config[name]) + provider = str(block.get("provider") or "").strip().lower() + provider_config = dict(block.get("config", {})) + legacy_base = provider_config.pop("api_base", None) + if legacy_base: + from ._oss_providers import EMBEDDER_PROVIDERS, LLM_PROVIDERS + + provider_def = ( + LLM_PROVIDERS if name == "llm" else EMBEDDER_PROVIDERS + ).get(provider, {}) + canonical_key = provider_def.get("base_url_key") + if canonical_key: + provider_config.setdefault(canonical_key, legacy_base) + block["config"] = provider_config + return block + vector_store = dict(oss_config["vector_store"]) vs_config = dict(vector_store.get("config", {})) @@ -182,8 +199,8 @@ class OSSBackend(Mem0Backend): config = { "vector_store": vector_store, - "llm": oss_config["llm"], - "embedder": oss_config["embedder"], + "llm": _provider_block("llm"), + "embedder": _provider_block("embedder"), "version": "v1.1", } self._memory = Memory.from_config(config) diff --git a/plugins/memory/mem0/_oss_providers.py b/plugins/memory/mem0/_oss_providers.py index fa36e73a9..3eb96e04c 100644 --- a/plugins/memory/mem0/_oss_providers.py +++ b/plugins/memory/mem0/_oss_providers.py @@ -11,12 +11,14 @@ LLM_PROVIDERS: dict[str, dict[str, Any]] = { "needs_key": True, "env_var": "OPENAI_API_KEY", "default_model": "gpt-5-mini", + "base_url_key": "openai_base_url", }, "ollama": { "label": "Ollama (local)", "needs_key": False, "default_model": "llama3.1:8b", "default_url": "http://localhost:11434", + "base_url_key": "ollama_base_url", "pip_dep": "ollama", }, } @@ -27,6 +29,7 @@ EMBEDDER_PROVIDERS: dict[str, dict[str, Any]] = { "needs_key": True, "env_var": "OPENAI_API_KEY", "default_model": "text-embedding-3-small", + "base_url_key": "openai_base_url", "dims": 1536, }, "ollama": { @@ -34,6 +37,7 @@ EMBEDDER_PROVIDERS: dict[str, dict[str, Any]] = { "needs_key": False, "default_model": "nomic-embed-text", "default_url": "http://localhost:11434", + "base_url_key": "ollama_base_url", "dims": 768, "pip_dep": "ollama", }, diff --git a/plugins/memory/mem0/_setup.py b/plugins/memory/mem0/_setup.py index a331ef3a8..9883d8f2c 100644 --- a/plugins/memory/mem0/_setup.py +++ b/plugins/memory/mem0/_setup.py @@ -135,15 +135,17 @@ def build_oss_config(flags: dict[str, str]) -> tuple[dict, dict[str, str]]: llm_def = LLM_PROVIDERS[llm_id] llm_model = flags.get("oss_llm_model") or llm_def["default_model"] llm_config: dict[str, Any] = {"model": llm_model} - if "default_url" in llm_def: - llm_config["ollama_base_url"] = flags.get("oss_llm_url") or llm_def["default_url"] + llm_url = flags.get("oss_llm_url") or llm_def.get("default_url") + if llm_url and llm_def.get("base_url_key"): + llm_config[llm_def["base_url_key"]] = llm_url embedder_id = flags.get("oss_embedder", "openai") embedder_def = EMBEDDER_PROVIDERS[embedder_id] embedder_model = flags.get("oss_embedder_model") or embedder_def["default_model"] embedder_config: dict[str, Any] = {"model": embedder_model} - if "default_url" in embedder_def: - embedder_config["ollama_base_url"] = flags.get("oss_embedder_url") or embedder_def["default_url"] + embedder_url = flags.get("oss_embedder_url") or embedder_def.get("default_url") + if embedder_url and embedder_def.get("base_url_key"): + embedder_config[embedder_def["base_url_key"]] = embedder_url dims = KNOWN_DIMS.get(embedder_model) if dims: embedder_config["embedding_dims"] = dims diff --git a/tests/plugins/memory/test_mem0_backend.py b/tests/plugins/memory/test_mem0_backend.py index 94f6d1443..6cb9846b6 100644 --- a/tests/plugins/memory/test_mem0_backend.py +++ b/tests/plugins/memory/test_mem0_backend.py @@ -1,5 +1,6 @@ """Tests for Mem0Backend abstraction — PlatformBackend, OSSBackend, SelfHostedBackend.""" +import copy import pytest from plugins.memory.mem0._backend import ( @@ -191,6 +192,45 @@ class TestOSSBackend: result = backend.delete("m1") assert result == {"result": "Memory deleted.", "memory_id": "m1"} + def test_legacy_api_base_aliases_are_normalized_before_mem0_init(self, monkeypatch): + import sys + import types + + captured = {} + + class Memory: + @staticmethod + def from_config(config): + captured.update(config) + return FakeOSSMemory() + + # OSSBackend.__init__ does `from mem0 import Memory`. mem0 is a lazy + # optional dep absent from CI's env, so inject a stub module rather + # than importing the real package (which would ModuleNotFoundError). + stub_mem0 = types.ModuleType("mem0") + stub_mem0.Memory = Memory # type: ignore[attr-defined] + monkeypatch.setitem(sys.modules, "mem0", stub_mem0) + raw = { + "llm": { + "provider": "openai", + "config": {"model": "gpt-5-mini", "api_base": "https://llm.example/v1"}, + }, + "embedder": { + "provider": "ollama", + "config": {"model": "nomic-embed-text", "api_base": "http://ollama:11434"}, + }, + "vector_store": {"provider": "qdrant", "config": {}}, + } + before = copy.deepcopy(raw) + + OSSBackend(raw) + + assert captured["llm"]["config"]["openai_base_url"] == "https://llm.example/v1" + assert captured["embedder"]["config"]["ollama_base_url"] == "http://ollama:11434" + assert "api_base" not in captured["llm"]["config"] + assert "api_base" not in captured["embedder"]["config"] + assert raw == before + httpx = pytest.importorskip("httpx") diff --git a/tests/plugins/memory/test_mem0_setup.py b/tests/plugins/memory/test_mem0_setup.py index b4b85a586..cbe8353a4 100644 --- a/tests/plugins/memory/test_mem0_setup.py +++ b/tests/plugins/memory/test_mem0_setup.py @@ -96,11 +96,28 @@ class TestBuildOSSConfig: assert oss["vector_store"]["provider"] == "qdrant" assert env_writes["OPENAI_API_KEY"] == "sk-oai" + def test_openai_custom_urls_use_mem0_provider_specific_keys(self): + flags = parse_flags([ + "--mode", "oss", + "--oss-llm-key", "sk-oai", + "--oss-llm-url", "https://llm.example/v1", + "--oss-embedder-url", "https://embed.example/v1", + ]) + + oss, _ = build_oss_config(flags) + + assert oss["llm"]["config"]["openai_base_url"] == "https://llm.example/v1" + assert oss["embedder"]["config"]["openai_base_url"] == "https://embed.example/v1" + assert "api_base" not in oss["llm"]["config"] + assert "api_base" not in oss["embedder"]["config"] + def test_ollama_no_key_needed(self): flags = parse_flags(["--mode", "oss", "--oss-llm", "ollama", "--oss-embedder", "ollama"]) oss, env_writes = build_oss_config(flags) assert oss["llm"]["provider"] == "ollama" assert "model" in oss["llm"]["config"] + assert oss["llm"]["config"]["ollama_base_url"] == "http://localhost:11434" + assert oss["embedder"]["config"]["ollama_base_url"] == "http://localhost:11434" assert env_writes == {} def test_embedder_reuses_llm_key(self):