From a5dab4279b007f42ced338fb4dbafdd7c326d07e Mon Sep 17 00:00:00 2001 From: kartik-mem0 Date: Thu, 13 Aug 2026 19:38:49 +0530 Subject: [PATCH] fix(ts-oss): honor LMSTUDIO_BASE_URL env var in LM Studio LLM and embedder Both the TS OSS LM Studio LLM and embedder hardcoded the base URL default without checking the LMSTUDIO_BASE_URL environment variable, unlike the Together provider which already supports TOGETHER_API_BASE. Precedence is now explicit config value > LMSTUDIO_BASE_URL env var > existing default. --- mem0-ts/src/oss/src/embeddings/lmstudio.ts | 6 +- mem0-ts/src/oss/src/llms/lmstudio.ts | 3 +- .../src/oss/tests/lmstudio-embedder.test.ts | 58 +++++++++++++++++-- mem0-ts/src/oss/tests/lmstudio-llm.test.ts | 48 +++++++++++++-- 4 files changed, 105 insertions(+), 10 deletions(-) diff --git a/mem0-ts/src/oss/src/embeddings/lmstudio.ts b/mem0-ts/src/oss/src/embeddings/lmstudio.ts index 509a0a0da..79bf16c74 100644 --- a/mem0-ts/src/oss/src/embeddings/lmstudio.ts +++ b/mem0-ts/src/oss/src/embeddings/lmstudio.ts @@ -12,7 +12,11 @@ export class LMStudioEmbedder implements Embedder { private model: string; constructor(config: EmbeddingConfig) { - const baseURL = config.baseURL ?? config.url ?? DEFAULT_BASE_URL; + const baseURL = + config.baseURL ?? + config.url ?? + process.env.LMSTUDIO_BASE_URL ?? + DEFAULT_BASE_URL; const apiKey = config.apiKey || DEFAULT_LMSTUDIO_API_KEY; this.openai = new OpenAI({ apiKey, baseURL: String(baseURL) }); this.model = config.model || DEFAULT_MODEL; diff --git a/mem0-ts/src/oss/src/llms/lmstudio.ts b/mem0-ts/src/oss/src/llms/lmstudio.ts index eb8c09109..ba9e0a5c6 100644 --- a/mem0-ts/src/oss/src/llms/lmstudio.ts +++ b/mem0-ts/src/oss/src/llms/lmstudio.ts @@ -12,7 +12,8 @@ export class LMStudioLLM extends OpenAILLM { super({ ...config, apiKey: config.apiKey || DEFAULT_LMSTUDIO_API_KEY, - baseURL: config.baseURL ?? DEFAULT_BASE_URL, + baseURL: + config.baseURL ?? process.env.LMSTUDIO_BASE_URL ?? DEFAULT_BASE_URL, model: config.model || DEFAULT_MODEL, }); } diff --git a/mem0-ts/src/oss/tests/lmstudio-embedder.test.ts b/mem0-ts/src/oss/tests/lmstudio-embedder.test.ts index 9c6b1b80c..1efbbaad1 100644 --- a/mem0-ts/src/oss/tests/lmstudio-embedder.test.ts +++ b/mem0-ts/src/oss/tests/lmstudio-embedder.test.ts @@ -10,14 +10,64 @@ const mockCreate = jest.fn().mockResolvedValue({ data: [{ embedding: mockEmbedding }], }); +let capturedConstructorArgs: any; + jest.mock("openai", () => { - return jest.fn().mockImplementation(() => ({ - embeddings: { create: mockCreate }, - })); + return jest.fn().mockImplementation((args: any) => { + capturedConstructorArgs = args; + return { + embeddings: { create: mockCreate }, + }; + }); }); describe("LMStudioEmbedder (unit)", () => { - beforeEach(() => mockCreate.mockClear()); + beforeEach(() => { + mockCreate.mockClear(); + capturedConstructorArgs = undefined; + delete process.env.LMSTUDIO_BASE_URL; + }); + + afterEach(() => { + delete process.env.LMSTUDIO_BASE_URL; + }); + + it("uses the default base URL when neither config nor env var is set", async () => { + const embedder = new LMStudioEmbedder({ model: "test-model" }); + + await embedder.embed("hello"); + + expect(capturedConstructorArgs).toMatchObject({ + baseURL: "http://localhost:1234/v1", + }); + }); + + it("uses LMSTUDIO_BASE_URL when config does not provide a base URL", async () => { + process.env.LMSTUDIO_BASE_URL = "http://example.test:5678/v1"; + + const embedder = new LMStudioEmbedder({ model: "test-model" }); + + await embedder.embed("hello"); + + expect(capturedConstructorArgs).toMatchObject({ + baseURL: "http://example.test:5678/v1", + }); + }); + + it("prefers an explicit config baseURL over LMSTUDIO_BASE_URL", async () => { + process.env.LMSTUDIO_BASE_URL = "http://example.test:5678/v1"; + + const embedder = new LMStudioEmbedder({ + model: "test-model", + baseURL: "http://config.test:9999/v1", + }); + + await embedder.embed("hello"); + + expect(capturedConstructorArgs).toMatchObject({ + baseURL: "http://config.test:9999/v1", + }); + }); it("embed() calls OpenAI with encoding_format float and returns vector", async () => { const embedder = new LMStudioEmbedder({ diff --git a/mem0-ts/src/oss/tests/lmstudio-llm.test.ts b/mem0-ts/src/oss/tests/lmstudio-llm.test.ts index 9edeff548..84add05f4 100644 --- a/mem0-ts/src/oss/tests/lmstudio-llm.test.ts +++ b/mem0-ts/src/oss/tests/lmstudio-llm.test.ts @@ -5,16 +5,56 @@ import { LMStudioLLM } from "../src/llms/lmstudio"; +let capturedConstructorArgs: any; const mockCreate = jest.fn(); jest.mock("openai", () => { - return jest.fn().mockImplementation(() => ({ - chat: { completions: { create: mockCreate } }, - })); + return jest.fn().mockImplementation((args: any) => { + capturedConstructorArgs = args; + return { + chat: { completions: { create: mockCreate } }, + }; + }); }); describe("LMStudioLLM (unit)", () => { - beforeEach(() => mockCreate.mockClear()); + beforeEach(() => { + mockCreate.mockClear(); + capturedConstructorArgs = undefined; + delete process.env.LMSTUDIO_BASE_URL; + }); + + afterEach(() => { + delete process.env.LMSTUDIO_BASE_URL; + }); + + it("uses the default base URL when neither config nor env var is set", () => { + new LMStudioLLM({}); + + expect(capturedConstructorArgs).toMatchObject({ + baseURL: "http://localhost:1234/v1", + }); + }); + + it("uses LMSTUDIO_BASE_URL when config does not provide a base URL", () => { + process.env.LMSTUDIO_BASE_URL = "http://example.test:5678/v1"; + + new LMStudioLLM({}); + + expect(capturedConstructorArgs).toMatchObject({ + baseURL: "http://example.test:5678/v1", + }); + }); + + it("prefers an explicit config baseURL over LMSTUDIO_BASE_URL", () => { + process.env.LMSTUDIO_BASE_URL = "http://example.test:5678/v1"; + + new LMStudioLLM({ baseURL: "http://config.test:9999/v1" }); + + expect(capturedConstructorArgs).toMatchObject({ + baseURL: "http://config.test:9999/v1", + }); + }); it("generateResponse() returns a text response", async () => { mockCreate.mockResolvedValueOnce({