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.
This commit is contained in:
Teknium 2026-07-17 05:42:43 -07:00
parent 4c0546c9cc
commit 07f07c7b51
5 changed files with 86 additions and 6 deletions

View file

@ -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)

View file

@ -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",
},

View file

@ -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

View file

@ -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")

View file

@ -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):