Files
mem0/mem0-ts/src/oss/tests/config-manager.test.ts
T

922 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// <reference types="jest" />
import { ConfigManager } from "../src/config/manager";
import { LLMFactory } from "../src/utils/factory";
describe("ConfigManager", () => {
describe("mergeConfig - dimension handling", () => {
const baseLlm = {
provider: "openai",
config: { apiKey: "test-key" },
};
it("should leave dimension undefined when no explicit dimension or embeddingDims provided", () => {
const config = ConfigManager.mergeConfig({
embedder: { provider: "openai", config: { apiKey: "test-key" } },
vectorStore: { provider: "memory", config: { collectionName: "test" } },
llm: baseLlm,
});
// Dimension should be undefined so Memory._autoInitialize() will
// auto-detect it via a probe embedding at runtime.
expect(config.vectorStore.config.dimension).toBeUndefined();
});
it("should use embeddingDims from embedder config when provided", () => {
const config = ConfigManager.mergeConfig({
embedder: {
provider: "ollama",
config: { model: "nomic-embed-text", embeddingDims: 768 },
},
vectorStore: { provider: "qdrant", config: { collectionName: "test" } },
llm: baseLlm,
});
expect(config.vectorStore.config.dimension).toBe(768);
});
it("should prefer explicit vector store dimension over embedder dims", () => {
const config = ConfigManager.mergeConfig({
embedder: {
provider: "ollama",
config: { model: "nomic-embed-text", embeddingDims: 768 },
},
vectorStore: {
provider: "qdrant",
config: { collectionName: "test", dimension: 1024 },
},
llm: baseLlm,
});
expect(config.vectorStore.config.dimension).toBe(1024);
});
it("should leave dimension undefined when using a custom client without explicit dims", () => {
const mockClient = { someMethod: () => {} };
const config = ConfigManager.mergeConfig({
embedder: {
provider: "ollama",
config: { model: "nomic-embed-text" },
},
vectorStore: {
provider: "qdrant",
config: { collectionName: "test", client: mockClient },
},
llm: baseLlm,
});
// No embeddingDims and no explicit dimension → should be undefined
// for auto-detection at runtime.
expect(config.vectorStore.config.dimension).toBeUndefined();
});
it("should use embeddingDims when using a custom client", () => {
const mockClient = { someMethod: () => {} };
const config = ConfigManager.mergeConfig({
embedder: {
provider: "ollama",
config: { model: "nomic-embed-text", embeddingDims: 768 },
},
vectorStore: {
provider: "qdrant",
config: { collectionName: "test", client: mockClient },
},
llm: baseLlm,
});
expect(config.vectorStore.config.dimension).toBe(768);
});
});
describe("mergeConfig - LLM url passthrough for Ollama", () => {
const baseEmbedder = {
provider: "openai",
config: { apiKey: "test-key" },
};
const baseVectorStore = {
provider: "memory",
config: { collectionName: "test" },
};
it("should preserve url in LLM config when provided", () => {
const config = ConfigManager.mergeConfig({
embedder: baseEmbedder,
vectorStore: baseVectorStore,
llm: {
provider: "ollama",
config: { model: "llama3.2:3b", url: "http://10.0.0.100:11434" },
},
});
expect(config.llm.config.url).toBe("http://10.0.0.100:11434");
});
it("should prefer baseURL over url when both are provided", () => {
const config = ConfigManager.mergeConfig({
embedder: baseEmbedder,
vectorStore: baseVectorStore,
llm: {
provider: "ollama",
config: {
model: "llama3.2:3b",
baseURL: "http://custom:11434",
url: "http://fallback:11434",
},
},
});
expect(config.llm.config.baseURL).toBe("http://custom:11434");
expect(config.llm.config.url).toBe("http://fallback:11434");
});
it("should use url as baseURL fallback when no baseURL provided (issue #4715)", () => {
const config = ConfigManager.mergeConfig({
embedder: baseEmbedder,
vectorStore: baseVectorStore,
llm: {
provider: "ollama",
config: { model: "llama3.1:8b", url: "http://my-ollama-host:11434" },
},
});
expect(config.llm.config.baseURL).toBe("http://my-ollama-host:11434");
expect(config.llm.config.url).toBe("http://my-ollama-host:11434");
});
it("should not fall back to the OpenAI default baseURL for a non-OpenAI provider", () => {
const config = ConfigManager.mergeConfig({
embedder: baseEmbedder,
vectorStore: baseVectorStore,
llm: {
provider: "ollama",
config: { model: "llama3.2:3b" },
},
});
expect(config.llm.config.url).toBeUndefined();
// 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", () => {
const config = ConfigManager.mergeConfig({
embedder: baseEmbedder,
vectorStore: baseVectorStore,
llm: {
provider: "vllm",
config: {
model: "Qwen/Qwen2.5-32B-Instruct",
vllm_base_url: "http://localhost:8000/v1",
},
},
});
expect(config.llm.config.baseURL).toBe("http://localhost:8000/v1");
});
it("does not inject the OpenAI baseURL default for vLLM", () => {
const config = ConfigManager.mergeConfig({
embedder: baseEmbedder,
vectorStore: baseVectorStore,
llm: {
provider: "vllm",
config: { model: "Qwen/Qwen2.5-32B-Instruct" },
},
});
expect(config.llm.config.baseURL).toBeUndefined();
});
it("should preserve url in embedder config (existing behavior)", () => {
const config = ConfigManager.mergeConfig({
embedder: {
provider: "ollama",
config: {
model: "nomic-embed-text",
url: "http://10.0.0.100:11434",
},
},
vectorStore: baseVectorStore,
llm: {
provider: "ollama",
config: { model: "llama3.2:3b", url: "http://10.0.0.100:11434" },
},
});
expect(config.embedder.config.url).toBe("http://10.0.0.100:11434");
expect(config.llm.config.url).toBe("http://10.0.0.100:11434");
});
});
// ─────────────────────────────────────────────────────────────────────
// LM Studio snake_case normalization
// ─────────────────────────────────────────────────────────────────────
describe("mergeConfig - LM Studio embedder config", () => {
const baseLlm = { provider: "openai", config: { apiKey: "k" } };
it("normalizes lmstudio_base_url to baseURL for embedder", () => {
const cfg = ConfigManager.mergeConfig({
embedder: {
provider: "lmstudio",
config: {
model: "nomic-embed-text-v1.5",
lmstudio_base_url: "http://192.168.1.1:1234/v1",
} as any,
},
vectorStore: { provider: "memory", config: {} },
llm: baseLlm,
});
expect(cfg.embedder.provider).toBe("lmstudio");
expect(cfg.embedder.config.baseURL).toBe("http://192.168.1.1:1234/v1");
expect(cfg.embedder.config.model).toBe("nomic-embed-text-v1.5");
});
it("normalizes embedding_dims to embeddingDims for embedder", () => {
const cfg = ConfigManager.mergeConfig({
embedder: {
provider: "lmstudio",
config: {
model: "nomic-embed-text-v1.5",
embedding_dims: 768,
} as any,
},
vectorStore: { provider: "memory", config: {} },
llm: baseLlm,
});
expect(cfg.embedder.config.embeddingDims).toBe(768);
});
it("prefers camelCase baseURL over snake_case lmstudio_base_url", () => {
const cfg = ConfigManager.mergeConfig({
embedder: {
provider: "lmstudio",
config: {
model: "test",
baseURL: "http://camel:1234/v1",
lmstudio_base_url: "http://snake:1234/v1",
} as any,
},
vectorStore: { provider: "memory", config: {} },
llm: baseLlm,
});
expect(cfg.embedder.config.baseURL).toBe("http://camel:1234/v1");
});
it("prefers camelCase embeddingDims over snake_case embedding_dims", () => {
const cfg = ConfigManager.mergeConfig({
embedder: {
provider: "lmstudio",
config: {
model: "test",
embeddingDims: 1536,
embedding_dims: 768,
} as any,
},
vectorStore: { provider: "memory", config: {} },
llm: baseLlm,
});
expect(cfg.embedder.config.embeddingDims).toBe(1536);
});
it("passes through camelCase config without issues", () => {
const cfg = ConfigManager.mergeConfig({
embedder: {
provider: "lmstudio",
config: {
model: "nomic-embed-text-v1.5",
baseURL: "http://localhost:1234/v1",
embeddingDims: 768,
},
},
vectorStore: { provider: "memory", config: {} },
llm: baseLlm,
});
expect(cfg.embedder.config.baseURL).toBe("http://localhost:1234/v1");
expect(cfg.embedder.config.embeddingDims).toBe(768);
});
});
describe("mergeConfig - FastEmbed defaults", () => {
const baseLlm = { provider: "openai", config: { apiKey: "k" } };
it("does not inject the OpenAI default embedder model into fastembed", () => {
const cfg = ConfigManager.mergeConfig({
embedder: {
provider: "fastembed",
config: {},
},
vectorStore: { provider: "memory", config: {} },
llm: baseLlm,
});
expect(cfg.embedder.provider).toBe("fastembed");
expect(cfg.embedder.config.model).toBeUndefined();
});
it("treats FastEmbed provider casing the same way as the factory", () => {
const cfg = ConfigManager.mergeConfig({
embedder: {
provider: "FastEmbed",
config: {},
},
vectorStore: { provider: "memory", config: {} },
llm: baseLlm,
});
expect(cfg.embedder.provider).toBe("FastEmbed");
expect(cfg.embedder.config.model).toBeUndefined();
});
});
describe("mergeConfig - LM Studio LLM config", () => {
const baseEmbedder = { provider: "openai", config: { apiKey: "k" } };
it("normalizes lmstudio_base_url to baseURL for LLM", () => {
const cfg = ConfigManager.mergeConfig({
embedder: baseEmbedder,
vectorStore: { provider: "memory", config: {} },
llm: {
provider: "lmstudio",
config: {
model: "meta-llama-3.1",
lmstudio_base_url: "http://192.168.1.1:1234/v1",
} as any,
},
});
expect(cfg.llm.provider).toBe("lmstudio");
expect(cfg.llm.config.baseURL).toBe("http://192.168.1.1:1234/v1");
expect(cfg.llm.config.model).toBe("meta-llama-3.1");
});
it("prefers camelCase baseURL over lmstudio_base_url for LLM", () => {
const cfg = ConfigManager.mergeConfig({
embedder: baseEmbedder,
vectorStore: { provider: "memory", config: {} },
llm: {
provider: "lmstudio",
config: {
baseURL: "http://camel:1234/v1",
lmstudio_base_url: "http://snake:1234/v1",
} as any,
},
});
expect(cfg.llm.config.baseURL).toBe("http://camel:1234/v1");
});
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" } },
});
// 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);
}
});
});
describe("mergeConfig - AWS Bedrock credential passthrough", () => {
const baseEmbedder = { provider: "openai", config: { apiKey: "k" } };
const baseVectorStore = { provider: "memory", config: {} };
it("preserves explicit AWS Bedrock credentials through MemoryConfigSchema.parse", () => {
const cfg = ConfigManager.mergeConfig({
embedder: baseEmbedder,
vectorStore: baseVectorStore,
llm: {
provider: "aws_bedrock",
config: {
model: "anthropic.claude-3-sonnet-20240229-v1:0",
awsRegion: "us-east-1",
awsAccessKeyId: "AKIAEXAMPLE",
awsSecretAccessKey: "secret-example",
awsSessionToken: "session-example",
} as any,
},
});
// Regression: zod default parse strips undeclared keys, which silently
// dropped credentials before .passthrough() + typed fields were added.
expect(cfg.llm.config.awsRegion).toBe("us-east-1");
expect(cfg.llm.config.awsAccessKeyId).toBe("AKIAEXAMPLE");
expect(cfg.llm.config.awsSecretAccessKey).toBe("secret-example");
expect(cfg.llm.config.awsSessionToken).toBe("session-example");
});
it("normalizes snake_case AWS credentials from the raw config", () => {
const cfg = ConfigManager.mergeConfig({
embedder: baseEmbedder,
vectorStore: baseVectorStore,
llm: {
provider: "aws_bedrock",
config: {
model: "anthropic.claude-3-sonnet-20240229-v1:0",
aws_region: "eu-west-1",
aws_access_key_id: "AKIASNAKE",
aws_secret_access_key: "snake-secret",
} as any,
},
});
expect(cfg.llm.config.awsRegion).toBe("eu-west-1");
expect(cfg.llm.config.awsAccessKeyId).toBe("AKIASNAKE");
expect(cfg.llm.config.awsSecretAccessKey).toBe("snake-secret");
});
});
describe("mergeConfig - full OpenClaw-style LM Studio config", () => {
it("handles the exact config from issue #4235", () => {
const cfg = ConfigManager.mergeConfig({
embedder: {
provider: "lmstudio",
config: {
model: "text-embedding-gte-qwen2-1.5b-instruct",
embedding_dims: 1536,
lmstudio_base_url: "http://192.168.200.83:1234/v1",
} as any,
},
vectorStore: {
provider: "qdrant",
config: {
host: "192.168.200.12",
port: 6333,
checkCompatibility: false,
},
},
llm: {
provider: "lmstudio",
config: {
model: "openai/gpt-oss-20b",
lmstudio_base_url: "http://192.168.200.83:1234/v1",
} as any,
},
});
expect(cfg.embedder.provider).toBe("lmstudio");
expect(cfg.embedder.config.baseURL).toBe("http://192.168.200.83:1234/v1");
expect(cfg.embedder.config.model).toBe(
"text-embedding-gte-qwen2-1.5b-instruct",
);
expect(cfg.embedder.config.embeddingDims).toBe(1536);
expect(cfg.llm.provider).toBe("lmstudio");
expect(cfg.llm.config.baseURL).toBe("http://192.168.200.83:1234/v1");
expect(cfg.llm.config.model).toBe("openai/gpt-oss-20b");
expect(cfg.vectorStore.provider).toBe("qdrant");
expect(cfg.vectorStore.config.host).toBe("192.168.200.12");
expect(cfg.vectorStore.config.port).toBe(6333);
});
});
describe("mergeConfig - provider-specific embedder fields", () => {
// The embedder config used to be rebuilt from a fixed key list, which
// dropped every provider-specific field before the embedder was
// constructed. Vertex AI then authenticated against whatever ambient
// project ADC resolved to and ignored the configured task types.
it("preserves Vertex AI fields through the merge", () => {
const cfg = ConfigManager.mergeConfig({
embedder: {
provider: "vertexai",
config: {
model: "gemini-embedding-001",
googleProjectId: "my-proj",
location: "europe-west4",
vertexCredentialsJson: "/creds.json",
memoryAddEmbeddingType: "SEMANTIC_SIMILARITY",
},
},
vectorStore: { provider: "memory", config: { collectionName: "test" } },
llm: { provider: "openai", config: { apiKey: "test-key" } },
});
expect(cfg.embedder.config).toMatchObject({
model: "gemini-embedding-001",
googleProjectId: "my-proj",
location: "europe-west4",
vertexCredentialsJson: "/creds.json",
memoryAddEmbeddingType: "SEMANTIC_SIMILARITY",
});
});
it("still lets normalized values win over the raw user config", () => {
const cfg = ConfigManager.mergeConfig({
embedder: {
provider: "lmstudio",
config: {
lmstudio_base_url: "http://localhost:1234/v1",
embedding_dims: 768,
},
} as never,
vectorStore: { provider: "memory", config: { collectionName: "test" } },
llm: { provider: "openai", config: { apiKey: "test-key" } },
});
expect(cfg.embedder.config.baseURL).toBe("http://localhost:1234/v1");
expect(cfg.embedder.config.embeddingDims).toBe(768);
// Snake_case aliases are normalized, not passed through to the provider.
expect(cfg.embedder.config).not.toHaveProperty("lmstudio_base_url");
expect(cfg.embedder.config).not.toHaveProperty("embedding_dims");
});
});
describe("mergeConfig - vector store provider normalization", () => {
const baseLlm = { provider: "openai", config: { apiKey: "test-key" } };
const baseEmbedder = { provider: "openai", config: { apiKey: "test-key" } };
it.each(["Memory", "DATABRICKS", "QdRaNt"])(
"lowercases the vector store provider %p",
(provider) => {
const config = ConfigManager.mergeConfig({
embedder: baseEmbedder,
vectorStore: { provider, config: { collectionName: "test" } },
llm: baseLlm,
});
expect(config.vectorStore.provider).toBe(provider.toLowerCase());
},
);
it("still falls back to the default provider when none is given", () => {
const config = ConfigManager.mergeConfig({
embedder: baseEmbedder,
vectorStore: { config: { collectionName: "test" } } as any,
llm: baseLlm,
});
expect(config.vectorStore.provider).toBe("memory");
});
});
});
// ─────────────────────────────────────────────────────────────────────────
// Memory class – LM Studio end-to-end flow (mocked factories)
// ─────────────────────────────────────────────────────────────────────────
describe("Memory – LM Studio end-to-end flow", () => {
let MemoryClass: any;
let mockEmbedderFactory: any;
let mockVectorStoreFactory: any;
let mockLlmFactory: any;
let mockHistoryFactory: any;
let mockEmbedder: any;
let mockVStore: any;
let mockLlm: any;
beforeEach(() => {
jest.resetModules();
mockEmbedder = {
embed: jest.fn().mockResolvedValue(new Array(768).fill(0.1)),
embedBatch: jest.fn().mockResolvedValue([new Array(768).fill(0.1)]),
};
mockVStore = {
insert: jest.fn().mockResolvedValue(undefined),
search: jest.fn().mockResolvedValue([]),
get: jest.fn().mockResolvedValue(null),
update: jest.fn().mockResolvedValue(undefined),
delete: jest.fn().mockResolvedValue(undefined),
deleteCol: jest.fn().mockResolvedValue(undefined),
list: jest.fn().mockResolvedValue([[], 0]),
getUserId: jest.fn().mockResolvedValue("test-user-id"),
setUserId: jest.fn().mockResolvedValue(undefined),
initialize: jest.fn().mockResolvedValue(undefined),
};
mockLlm = {
generateResponse: jest.fn().mockResolvedValue('{"facts":[]}'),
};
mockEmbedderFactory = { create: jest.fn().mockReturnValue(mockEmbedder) };
mockVectorStoreFactory = { create: jest.fn().mockReturnValue(mockVStore) };
mockLlmFactory = { create: jest.fn().mockReturnValue(mockLlm) };
mockHistoryFactory = {
create: jest.fn().mockReturnValue({
addHistory: jest.fn().mockResolvedValue(undefined),
getHistory: jest.fn().mockResolvedValue([]),
reset: jest.fn().mockResolvedValue(undefined),
}),
};
jest.doMock("../src/utils/factory", () => ({
EmbedderFactory: mockEmbedderFactory,
VectorStoreFactory: mockVectorStoreFactory,
LLMFactory: mockLlmFactory,
HistoryManagerFactory: mockHistoryFactory,
}));
jest.doMock("../src/utils/telemetry", () => ({
captureClientEvent: jest.fn().mockResolvedValue(undefined),
isTelemetryEnabled: jest.fn(() => false),
}));
MemoryClass = require("../src/memory").Memory;
});
afterEach(() => {
jest.restoreAllMocks();
jest.resetModules();
});
it("creates Memory with lmstudio embedder and llm providers", async () => {
const mem = new MemoryClass({
embedder: {
provider: "lmstudio",
config: {
model: "nomic-embed-text-v1.5",
baseURL: "http://localhost:1234/v1",
},
},
vectorStore: { provider: "memory", config: { collectionName: "test" } },
llm: {
provider: "lmstudio",
config: {
model: "meta-llama-3.1-70b",
baseURL: "http://localhost:1234/v1",
},
},
disableHistory: true,
});
await mem.getAll({ filters: { user_id: "u1" } });
expect(mockEmbedderFactory.create).toHaveBeenCalledWith(
"lmstudio",
expect.objectContaining({
model: "nomic-embed-text-v1.5",
baseURL: "http://localhost:1234/v1",
}),
);
expect(mockLlmFactory.create).toHaveBeenCalledWith(
"lmstudio",
expect.objectContaining({
model: "meta-llama-3.1-70b",
baseURL: "http://localhost:1234/v1",
}),
);
});
it("auto-detects embedding dimension via probe with lmstudio", async () => {
const mem = new MemoryClass({
embedder: {
provider: "lmstudio",
config: {
model: "nomic-embed-text-v1.5",
baseURL: "http://localhost:1234/v1",
},
},
vectorStore: { provider: "qdrant", config: { collectionName: "test" } },
llm: {
provider: "lmstudio",
config: { baseURL: "http://localhost:1234/v1" },
},
disableHistory: true,
});
await mem.getAll({ filters: { user_id: "u1" } });
expect(mockEmbedder.embed).toHaveBeenCalledWith("dimension probe");
const vsCall = mockVectorStoreFactory.create.mock.calls[0];
expect(vsCall[1].dimension).toBe(768);
});
it("handles snake_case OpenClaw config through full Memory stack", async () => {
const mem = new MemoryClass({
embedder: {
provider: "lmstudio",
config: {
model: "text-embedding-gte-qwen2-1.5b-instruct",
embedding_dims: 1536,
lmstudio_base_url: "http://192.168.200.83:1234/v1",
} as any,
},
vectorStore: { provider: "memory", config: { collectionName: "test" } },
llm: {
provider: "lmstudio",
config: {
model: "openai/gpt-oss-20b",
lmstudio_base_url: "http://192.168.200.83:1234/v1",
} as any,
},
disableHistory: true,
});
await mem.getAll({ filters: { user_id: "u1" } });
expect(mockEmbedderFactory.create).toHaveBeenCalledWith(
"lmstudio",
expect.objectContaining({
model: "text-embedding-gte-qwen2-1.5b-instruct",
baseURL: "http://192.168.200.83:1234/v1",
}),
);
expect(mockLlmFactory.create).toHaveBeenCalledWith(
"lmstudio",
expect.objectContaining({
model: "openai/gpt-oss-20b",
baseURL: "http://192.168.200.83:1234/v1",
}),
);
});
it("search flow works with lmstudio embedder", async () => {
mockVStore.search.mockResolvedValueOnce([
{
id: "mem-1",
payload: {
data: "User likes hiking",
user_id: "u1",
hash: "abc123",
created_at: "2026-01-01",
},
score: 0.95,
},
]);
const mem = new MemoryClass({
embedder: {
provider: "lmstudio",
config: {
model: "nomic-embed-text-v1.5",
baseURL: "http://localhost:1234/v1",
embeddingDims: 768,
},
},
vectorStore: {
provider: "memory",
config: { collectionName: "test", dimension: 768 },
},
llm: {
provider: "lmstudio",
config: { baseURL: "http://localhost:1234/v1" },
},
disableHistory: true,
});
const result = await mem.search("What does the user like?", {
filters: { user_id: "u1" },
});
expect(mockEmbedder.embed).toHaveBeenCalledWith(
"What does the user like?",
"search",
);
expect(mockVStore.search).toHaveBeenCalled();
expect(result.results).toHaveLength(1);
expect(result.results[0].memory).toBe("User likes hiking");
});
it("preserves the FastEmbed provider default model through the Memory config path", async () => {
const mem = new MemoryClass({
embedder: {
provider: "fastembed",
config: {},
},
vectorStore: { provider: "memory", config: { collectionName: "test" } },
llm: {
provider: "openai",
config: { apiKey: "test-key" },
},
disableHistory: true,
});
await mem.getAll({ filters: { user_id: "u1" } });
expect(mockEmbedderFactory.create).toHaveBeenCalledWith(
"fastembed",
expect.objectContaining({
model: undefined,
}),
);
});
it("passes AWS Bedrock credentials to LLMFactory through the Memory stack", async () => {
const mem = new MemoryClass({
embedder: {
provider: "lmstudio",
config: {
model: "nomic-embed-text-v1.5",
baseURL: "http://localhost:1234/v1",
embeddingDims: 768,
},
},
vectorStore: {
provider: "memory",
config: { collectionName: "test", dimension: 768 },
},
llm: {
provider: "aws_bedrock",
config: {
model: "anthropic.claude-3-sonnet-20240229-v1:0",
awsRegion: "us-east-1",
awsAccessKeyId: "AKIAEXAMPLE",
awsSecretAccessKey: "secret-example",
},
},
disableHistory: true,
});
await mem.getAll({ filters: { user_id: "u1" } });
// Regression: credentials must survive ConfigManager -> Memory -> LLMFactory,
// otherwise Bedrock silently falls back to the ambient AWS credential chain.
expect(mockLlmFactory.create).toHaveBeenCalledWith(
"aws_bedrock",
expect.objectContaining({
model: "anthropic.claude-3-sonnet-20240229-v1:0",
awsRegion: "us-east-1",
awsAccessKeyId: "AKIAEXAMPLE",
awsSecretAccessKey: "secret-example",
}),
);
});
it("add flow works with lmstudio LLM for fact extraction", async () => {
mockLlm.generateResponse.mockResolvedValueOnce(
'{"facts":["User loves sushi"]}',
);
mockVStore.search.mockResolvedValue([]);
mockVStore.list.mockResolvedValue([[], 0]);
const mem = new MemoryClass({
embedder: {
provider: "lmstudio",
config: {
model: "nomic-embed-text-v1.5",
baseURL: "http://localhost:1234/v1",
embeddingDims: 768,
},
},
vectorStore: {
provider: "memory",
config: { collectionName: "test", dimension: 768 },
},
llm: {
provider: "lmstudio",
config: {
model: "meta-llama-3.1-70b",
baseURL: "http://localhost:1234/v1",
},
},
disableHistory: true,
});
await mem.add("I love sushi", { userId: "u1" });
expect(mockLlm.generateResponse).toHaveBeenCalled();
expect(mockEmbedder.embed).toHaveBeenCalled();
});
});