fix: use root LLM config as fallback for graph store instead of hardcoded OpenAI default (#4466)
Co-authored-by: utkarsh240799 <utkarsh240799@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
utkarsh240799
Claude Opus 4.6
parent
7a09663156
commit
06c25eb00b
@@ -34,12 +34,6 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
|
|||||||
username: process.env.NEO4J_USERNAME || "neo4j",
|
username: process.env.NEO4J_USERNAME || "neo4j",
|
||||||
password: process.env.NEO4J_PASSWORD || "password",
|
password: process.env.NEO4J_PASSWORD || "password",
|
||||||
},
|
},
|
||||||
llm: {
|
|
||||||
provider: "openai",
|
|
||||||
config: {
|
|
||||||
model: "gpt-4-turbo-preview",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
historyStore: {
|
historyStore: {
|
||||||
provider: "sqlite",
|
provider: "sqlite",
|
||||||
|
|||||||
@@ -80,18 +80,18 @@ export class MemoryGraph {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.llmProvider = "openai";
|
this.llmProvider = "openai";
|
||||||
|
let llmConfig = this.config.llm.config;
|
||||||
|
|
||||||
if (this.config.llm?.provider) {
|
if (this.config.llm?.provider) {
|
||||||
this.llmProvider = this.config.llm.provider;
|
this.llmProvider = this.config.llm.provider;
|
||||||
}
|
}
|
||||||
if (this.config.graphStore?.llm?.provider) {
|
if (this.config.graphStore?.llm?.provider) {
|
||||||
this.llmProvider = this.config.graphStore.llm.provider;
|
this.llmProvider = this.config.graphStore.llm.provider;
|
||||||
|
llmConfig = this.config.graphStore.llm.config ?? llmConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.llm = LLMFactory.create(this.llmProvider, this.config.llm.config);
|
this.llm = LLMFactory.create(this.llmProvider, llmConfig);
|
||||||
this.structuredLlm = LLMFactory.create(
|
this.structuredLlm = LLMFactory.create(this.llmProvider, llmConfig);
|
||||||
this.llmProvider,
|
|
||||||
this.config.llm.config,
|
|
||||||
);
|
|
||||||
this.threshold = 0.7;
|
this.threshold = 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -349,6 +349,78 @@ describe("ConfigManager", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// Graph store LLM config propagation (issue #3425)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
describe("mergeConfig - graph store LLM config (issue #3425)", () => {
|
||||||
|
const baseEmbedder = {
|
||||||
|
provider: "openai",
|
||||||
|
config: { apiKey: "test-key" },
|
||||||
|
};
|
||||||
|
const baseVectorStore = {
|
||||||
|
provider: "memory",
|
||||||
|
config: { collectionName: "test" },
|
||||||
|
};
|
||||||
|
const graphStoreNeo4j = {
|
||||||
|
provider: "neo4j",
|
||||||
|
config: {
|
||||||
|
url: "neo4j://localhost:7687",
|
||||||
|
username: "neo4j",
|
||||||
|
password: "password",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it("should NOT have a default graphStore.llm — root llm should be the fallback", () => {
|
||||||
|
const config = ConfigManager.mergeConfig({
|
||||||
|
embedder: baseEmbedder,
|
||||||
|
vectorStore: baseVectorStore,
|
||||||
|
llm: {
|
||||||
|
provider: "anthropic",
|
||||||
|
config: { model: "claude-sonnet-4-20250514" },
|
||||||
|
},
|
||||||
|
graphStore: graphStoreNeo4j,
|
||||||
|
});
|
||||||
|
|
||||||
|
// graphStore should NOT have its own llm after merge
|
||||||
|
expect(config.graphStore?.llm).toBeUndefined();
|
||||||
|
// root llm should be anthropic
|
||||||
|
expect(config.llm.provider).toBe("anthropic");
|
||||||
|
expect(config.llm.config.model).toBe("claude-sonnet-4-20250514");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should preserve explicit graphStore.llm when user provides it", () => {
|
||||||
|
const config = ConfigManager.mergeConfig({
|
||||||
|
embedder: baseEmbedder,
|
||||||
|
vectorStore: baseVectorStore,
|
||||||
|
llm: {
|
||||||
|
provider: "anthropic",
|
||||||
|
config: { model: "claude-sonnet-4-20250514" },
|
||||||
|
},
|
||||||
|
graphStore: {
|
||||||
|
...graphStoreNeo4j,
|
||||||
|
llm: { provider: "openai", config: { model: "gpt-4o" } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// graphStore should have its own llm
|
||||||
|
expect(config.graphStore?.llm?.provider).toBe("openai");
|
||||||
|
expect(config.graphStore?.llm?.config).toEqual({ model: "gpt-4o" });
|
||||||
|
// root llm should still be anthropic
|
||||||
|
expect(config.llm.provider).toBe("anthropic");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not have graphStore.llm when user does not provide one", () => {
|
||||||
|
const config = ConfigManager.mergeConfig({
|
||||||
|
embedder: baseEmbedder,
|
||||||
|
vectorStore: baseVectorStore,
|
||||||
|
llm: { provider: "openai", config: { model: "gpt-4o" } },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Default graphStore should not have llm
|
||||||
|
expect(config.graphStore?.llm).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
// Memory class – LM Studio end-to-end flow (mocked factories)
|
// Memory class – LM Studio end-to-end flow (mocked factories)
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -511,6 +511,137 @@ describe("Prompt construction — all json_object sites include 'json'", () => {
|
|||||||
// 5. Edge cases – malformed entity fields in _removeSpacesFromEntities
|
// 5. Edge cases – malformed entity fields in _removeSpacesFromEntities
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// 5a. LLM config propagation — graph store uses correct provider & config
|
||||||
|
// Regression test for https://github.com/mem0ai/mem0/issues/3425
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
describe("LLM config propagation to graph store (issue #3425)", () => {
|
||||||
|
const { LLMFactory } = require("../src/utils/factory");
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(LLMFactory.create as jest.Mock).mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses root llm config when no graphStore.llm is provided", () => {
|
||||||
|
const config = {
|
||||||
|
graphStore: {
|
||||||
|
config: {
|
||||||
|
url: "bolt://localhost:7687",
|
||||||
|
username: "neo4j",
|
||||||
|
password: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
embedder: { provider: "openai", config: {} },
|
||||||
|
llm: {
|
||||||
|
provider: "anthropic",
|
||||||
|
config: { model: "claude-sonnet-4-20250514", apiKey: "sk-ant-test" },
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
new MemoryGraph(config);
|
||||||
|
|
||||||
|
expect(LLMFactory.create).toHaveBeenCalledWith("anthropic", {
|
||||||
|
model: "claude-sonnet-4-20250514",
|
||||||
|
apiKey: "sk-ant-test",
|
||||||
|
});
|
||||||
|
// Both llm and structuredLlm should use the same config
|
||||||
|
expect(LLMFactory.create).toHaveBeenCalledTimes(2);
|
||||||
|
expect(LLMFactory.create).toHaveBeenNthCalledWith(1, "anthropic", {
|
||||||
|
model: "claude-sonnet-4-20250514",
|
||||||
|
apiKey: "sk-ant-test",
|
||||||
|
});
|
||||||
|
expect(LLMFactory.create).toHaveBeenNthCalledWith(2, "anthropic", {
|
||||||
|
model: "claude-sonnet-4-20250514",
|
||||||
|
apiKey: "sk-ant-test",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses graphStore.llm config when provided, overriding root llm", () => {
|
||||||
|
const config = {
|
||||||
|
graphStore: {
|
||||||
|
config: {
|
||||||
|
url: "bolt://localhost:7687",
|
||||||
|
username: "neo4j",
|
||||||
|
password: "test",
|
||||||
|
},
|
||||||
|
llm: {
|
||||||
|
provider: "openai",
|
||||||
|
config: { model: "gpt-4o", apiKey: "sk-openai-test" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
embedder: { provider: "openai", config: {} },
|
||||||
|
llm: {
|
||||||
|
provider: "anthropic",
|
||||||
|
config: { model: "claude-sonnet-4-20250514", apiKey: "sk-ant-test" },
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
new MemoryGraph(config);
|
||||||
|
|
||||||
|
// Should use graphStore.llm, NOT root llm
|
||||||
|
expect(LLMFactory.create).toHaveBeenNthCalledWith(1, "openai", {
|
||||||
|
model: "gpt-4o",
|
||||||
|
apiKey: "sk-openai-test",
|
||||||
|
});
|
||||||
|
expect(LLMFactory.create).toHaveBeenNthCalledWith(2, "openai", {
|
||||||
|
model: "gpt-4o",
|
||||||
|
apiKey: "sk-openai-test",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to root llm config when graphStore.llm.config is undefined", () => {
|
||||||
|
// Note: in practice, Zod schema requires config when graphStore.llm is
|
||||||
|
// present. This tests the defensive fallback in MemoryGraph itself.
|
||||||
|
const config = {
|
||||||
|
graphStore: {
|
||||||
|
config: {
|
||||||
|
url: "bolt://localhost:7687",
|
||||||
|
username: "neo4j",
|
||||||
|
password: "test",
|
||||||
|
},
|
||||||
|
llm: {
|
||||||
|
provider: "openai",
|
||||||
|
// config explicitly undefined
|
||||||
|
config: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
embedder: { provider: "openai", config: {} },
|
||||||
|
llm: {
|
||||||
|
provider: "anthropic",
|
||||||
|
config: { model: "claude-sonnet-4-20250514" },
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
new MemoryGraph(config);
|
||||||
|
|
||||||
|
// Provider from graphStore.llm, but config falls back to root llm.config
|
||||||
|
expect(LLMFactory.create).toHaveBeenNthCalledWith(1, "openai", {
|
||||||
|
model: "claude-sonnet-4-20250514",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("defaults to openai when neither root nor graphStore llm provider is set", () => {
|
||||||
|
const config = {
|
||||||
|
graphStore: {
|
||||||
|
config: {
|
||||||
|
url: "bolt://localhost:7687",
|
||||||
|
username: "neo4j",
|
||||||
|
password: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
embedder: { provider: "openai", config: {} },
|
||||||
|
llm: { config: { model: "gpt-4" } },
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
new MemoryGraph(config);
|
||||||
|
|
||||||
|
expect(LLMFactory.create).toHaveBeenNthCalledWith(1, "openai", {
|
||||||
|
model: "gpt-4",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("_removeSpacesFromEntities (via _establishNodesRelationsFromData)", () => {
|
describe("_removeSpacesFromEntities (via _establishNodesRelationsFromData)", () => {
|
||||||
it("normalises spaces and case in entity source/relationship/destination", async () => {
|
it("normalises spaces and case in entity source/relationship/destination", async () => {
|
||||||
mockGenerateResponse.mockResolvedValueOnce({
|
mockGenerateResponse.mockResolvedValueOnce({
|
||||||
|
|||||||
Reference in New Issue
Block a user