From 989c7da0fc8e4df6342dbb8c448af9816d60d24c Mon Sep 17 00:00:00 2001 From: Varun S G <143688508+warun7@users.noreply.github.com> Date: Thu, 24 Sep 2026 22:14:28 +0530 Subject: [PATCH] fix(oss): stop ConfigManager from injecting OpenAI's baseURL and model into other providers (#7350) --- mem0-ts/src/oss/src/config/manager.ts | 17 ++++-- mem0-ts/src/oss/tests/config-manager.test.ts | 60 ++++++++++++++++++-- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/mem0-ts/src/oss/src/config/manager.ts b/mem0-ts/src/oss/src/config/manager.ts index b2803d1b2..d2fa34b52 100644 --- a/mem0-ts/src/oss/src/config/manager.ts +++ b/mem0-ts/src/oss/src/config/manager.ts @@ -113,7 +113,18 @@ export class ConfigManager { const userConf = userConfig.llm?.config; const provider = userConfig.llm?.provider || DEFAULT_MEMORY_CONFIG.llm.provider; - let finalModel: string | any = defaultConf.model; + // DEFAULT_MEMORY_CONFIG.llm.config holds OpenAI's own defaults (baseURL and + // model). Handing those to any other provider shadows that provider's default + // *and* its env fallback (DEEPSEEK_API_BASE, XAI_API_BASE, ...), so a config + // copied from the docs for another provider ends up pointed at OpenAI with an + // OpenAI model name. vLLM already needed a carve-out here for exactly this + // reason; every non-OpenAI provider needs it. + const usesOpenAIDefaults = + provider.toLowerCase() === "openai" || + provider.toLowerCase() === "openai_structured"; + let finalModel: string | any = usesOpenAIDefaults + ? defaultConf.model + : undefined; if (userConf?.model && typeof userConf.model === "object") { finalModel = userConf.model; @@ -131,9 +142,7 @@ export class ConfigManager { | string | undefined) ?? userConf?.url ?? - (provider.toLowerCase() === "vllm" - ? undefined - : defaultConf.baseURL); + (usesOpenAIDefaults ? defaultConf.baseURL : undefined); const temperature = userConf?.temperature ?? (llmRaw?.temperature as number | undefined); diff --git a/mem0-ts/src/oss/tests/config-manager.test.ts b/mem0-ts/src/oss/tests/config-manager.test.ts index 7c1856ad8..b3304509a 100644 --- a/mem0-ts/src/oss/tests/config-manager.test.ts +++ b/mem0-ts/src/oss/tests/config-manager.test.ts @@ -1,5 +1,6 @@ /// import { ConfigManager } from "../src/config/manager"; +import { LLMFactory } from "../src/utils/factory"; describe("ConfigManager", () => { describe("mergeConfig - dimension handling", () => { @@ -141,7 +142,7 @@ describe("ConfigManager", () => { expect(config.llm.config.url).toBe("http://my-ollama-host:11434"); }); - it("should use default baseURL when no url or baseURL provided", () => { + it("should not fall back to the OpenAI default baseURL for a non-OpenAI provider", () => { const config = ConfigManager.mergeConfig({ embedder: baseEmbedder, vectorStore: baseVectorStore, @@ -152,7 +153,9 @@ describe("ConfigManager", () => { }); expect(config.llm.config.url).toBeUndefined(); - expect(config.llm.config.baseURL).toBe("https://api.openai.com/v1"); + // OllamaLLM defaults to http://localhost:11434. The OpenAI default used to be + // injected here, which pointed OllamaLLM at OpenAI instead. + expect(config.llm.config.baseURL).toBeUndefined(); }); it("normalizes vllm_base_url to baseURL for vLLM", () => { @@ -367,14 +370,63 @@ describe("ConfigManager", () => { expect(cfg.llm.config.baseURL).toBe("http://camel:1234/v1"); }); - it("falls back to default baseURL when neither is provided for LLM", () => { + it("does not inject the OpenAI baseURL default for a non-OpenAI provider", () => { const cfg = ConfigManager.mergeConfig({ embedder: baseEmbedder, vectorStore: { provider: "memory", config: {} }, llm: { provider: "lmstudio", config: { model: "test-model" } }, }); - expect(cfg.llm.config.baseURL).toBe("https://api.openai.com/v1"); + // The provider supplies its own baseURL (http://localhost:1234/v1) when none is + // given. Injecting OpenAI's here shadowed it and sent lmstudio traffic to OpenAI. + expect(cfg.llm.config.baseURL).toBeUndefined(); + }); + + it("does not inject the OpenAI model default for a non-OpenAI provider", () => { + const cfg = ConfigManager.mergeConfig({ + embedder: baseEmbedder, + vectorStore: { provider: "memory", config: {} }, + llm: { provider: "deepseek", config: { apiKey: "k" } }, + }); + + // DeepSeekLLM falls back to "deepseek-chat" when model is unset. Injecting + // "gpt-5-mini" here made that fallback unreachable. + expect(cfg.llm.config.model).toBeUndefined(); + }); + + it("still applies the OpenAI defaults for the OpenAI providers", () => { + for (const provider of ["openai", "openai_structured"]) { + const cfg = ConfigManager.mergeConfig({ + embedder: baseEmbedder, + vectorStore: { provider: "memory", config: {} }, + llm: { provider, config: { apiKey: "k" } }, + }); + + expect(cfg.llm.config.baseURL).toBe("https://api.openai.com/v1"); + expect(cfg.llm.config.model).toBe("gpt-5-mini"); + } + }); + + it("lets each non-OpenAI provider resolve its own endpoint", () => { + const cases: Array<[string, string]> = [ + ["deepseek", "https://api.deepseek.com"], + ["xai", "https://api.x.ai/v1"], + ["lmstudio", "http://localhost:1234/v1"], + ]; + + for (const [provider, expected] of cases) { + const cfg = ConfigManager.mergeConfig({ + embedder: baseEmbedder, + vectorStore: { provider: "memory", config: {} }, + llm: { provider, config: { apiKey: "k" } }, + }); + + const built = LLMFactory.create(provider, cfg.llm.config); + // The client the provider actually built must not point at OpenAI. + const baseURL = + (built as any).openai?.baseURL ?? (built as any).baseURL; + expect(String(baseURL)).toBe(expected); + } }); });