From e95090e116de5376622fe10e10c5c266aa947f80 Mon Sep 17 00:00:00 2001 From: Giulio Leone Date: Fri, 13 Mar 2026 09:38:24 +0100 Subject: [PATCH] fix: add missing 'json' keyword to graph memory prompts (fixes #4248) (#4249) --- mem0-ts/src/oss/src/graphs/utils.ts | 2 + mem0-ts/src/oss/src/memory/graph_memory.ts | 2 +- .../oss/tests/graph-memory-parsing.test.ts | 498 ++++++++++++++++++ mem0-ts/src/oss/tests/graph-prompts.test.ts | 168 ++++++ 4 files changed, 669 insertions(+), 1 deletion(-) create mode 100644 mem0-ts/src/oss/tests/graph-memory-parsing.test.ts create mode 100644 mem0-ts/src/oss/tests/graph-prompts.test.ts diff --git a/mem0-ts/src/oss/src/graphs/utils.ts b/mem0-ts/src/oss/src/graphs/utils.ts index 949d0bc04..5e81fcbce 100644 --- a/mem0-ts/src/oss/src/graphs/utils.ts +++ b/mem0-ts/src/oss/src/graphs/utils.ts @@ -88,6 +88,8 @@ Memory Format: source -- relationship -- destination Provide a list of deletion instructions, each specifying the relationship to be deleted. + +Respond in JSON format. `; export function getDeleteMessages( diff --git a/mem0-ts/src/oss/src/memory/graph_memory.ts b/mem0-ts/src/oss/src/memory/graph_memory.ts index afd2e8d19..6de73940d 100644 --- a/mem0-ts/src/oss/src/memory/graph_memory.ts +++ b/mem0-ts/src/oss/src/memory/graph_memory.ts @@ -212,7 +212,7 @@ export class MemoryGraph { [ { role: "system", - content: `You are a smart assistant who understands entities and their types in a given text. If user message contains self reference such as 'I', 'me', 'my' etc. then use ${filters["userId"]} as the source entity. Extract all the entities from the text. ***DO NOT*** answer the question itself if the given text is a question.`, + content: `You are a smart assistant who understands entities and their types in a given text. If user message contains self reference such as 'I', 'me', 'my' etc. then use ${filters["userId"]} as the source entity. Extract all the entities from the text. ***DO NOT*** answer the question itself if the given text is a question. Respond in JSON format.`, }, { role: "user", content: data }, ], diff --git a/mem0-ts/src/oss/tests/graph-memory-parsing.test.ts b/mem0-ts/src/oss/tests/graph-memory-parsing.test.ts new file mode 100644 index 000000000..c8cd887e9 --- /dev/null +++ b/mem0-ts/src/oss/tests/graph-memory-parsing.test.ts @@ -0,0 +1,498 @@ +/** + * Regression tests for graph_memory.ts response parsing (issue #4248). + * + * Exercises the three json_object call sites in MemoryGraph with a mocked LLM: + * 1. _retrieveNodesFromData → entity extraction + * 2. _establishNodesRelationsFromData → relation extraction + * 3. _getDeleteEntitiesFromSearchOutput → deletion identification + * + * Covers: malformed LLM responses, missing fields, bad JSON in toolCalls, + * string-only responses, empty tool calls, and prompt construction. + * + * See: https://github.com/mem0ai/mem0/issues/4248 + */ + +import { MemoryGraph } from "../src/memory/graph_memory"; +import { EXTRACT_RELATIONS_PROMPT, getDeleteMessages } from "../src/graphs/utils"; + +// --------------------------------------------------------------------------- +// Mocks – we replace heavy dependencies so tests run without Neo4j / OpenAI +// --------------------------------------------------------------------------- + +// Mock neo4j-driver: provides a fake Driver with a no-op session +jest.mock("neo4j-driver", () => ({ + __esModule: true, + default: { + driver: jest.fn(() => ({ + session: () => ({ + run: jest.fn().mockResolvedValue({ records: [] }), + close: jest.fn(), + }), + })), + auth: { basic: jest.fn() }, + }, +})); + +// Mock factory so constructor doesn't try to instantiate real LLMs / embedders +const mockGenerateResponse = jest.fn(); +const mockGenerateChat = jest.fn(); +const mockEmbed = jest.fn().mockResolvedValue([0.1, 0.2, 0.3]); + +jest.mock("../src/utils/factory", () => ({ + LLMFactory: { + create: jest.fn(() => ({ + generateResponse: mockGenerateResponse, + generateChat: mockGenerateChat, + })), + }, + EmbedderFactory: { + create: jest.fn(() => ({ + embed: mockEmbed, + })), + }, +})); + +// Minimal config that satisfies the MemoryGraph constructor +function makeConfig(overrides: Record = {}) { + return { + graphStore: { + config: { + url: "bolt://localhost:7687", + username: "neo4j", + password: "test", + }, + ...overrides, + }, + embedder: { provider: "openai", config: {} }, + llm: { provider: "openai", config: {} }, + } as any; +} + +// Helper to access private methods via `any` cast +function graph(overrides: Record = {}): any { + return new MemoryGraph(makeConfig(overrides)); +} + +const FILTERS = { userId: "test-user" }; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +// ═══════════════════════════════════════════════════════════════════════════ +// 1. _retrieveNodesFromData – entity extraction +// ═══════════════════════════════════════════════════════════════════════════ + +describe("_retrieveNodesFromData", () => { + it("parses a well-formed extract_entities tool call", async () => { + mockGenerateResponse.mockResolvedValueOnce({ + toolCalls: [ + { + name: "extract_entities", + arguments: JSON.stringify({ + entities: [ + { entity: "Alice", entity_type: "person" }, + { entity: "Pizza", entity_type: "food" }, + ], + }), + }, + ], + }); + + const mg = graph(); + const result = await mg._retrieveNodesFromData("Alice likes pizza", FILTERS); + + expect(result).toEqual({ alice: "person", pizza: "food" }); + }); + + it("returns empty map when LLM returns a plain string", async () => { + mockGenerateResponse.mockResolvedValueOnce("I am a string, not an object"); + + const mg = graph(); + const result = await mg._retrieveNodesFromData("anything", FILTERS); + + expect(result).toEqual({}); + }); + + it("returns empty map when toolCalls is undefined", async () => { + mockGenerateResponse.mockResolvedValueOnce({}); + + const mg = graph(); + const result = await mg._retrieveNodesFromData("anything", FILTERS); + + expect(result).toEqual({}); + }); + + it("returns empty map when toolCalls is an empty array", async () => { + mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] }); + + const mg = graph(); + const result = await mg._retrieveNodesFromData("anything", FILTERS); + + expect(result).toEqual({}); + }); + + it("handles malformed JSON in tool call arguments gracefully", async () => { + mockGenerateResponse.mockResolvedValueOnce({ + toolCalls: [ + { name: "extract_entities", arguments: "NOT VALID JSON {{{" }, + ], + }); + + const mg = graph(); + // Should not throw — the catch block in the source logs the error + const result = await mg._retrieveNodesFromData("anything", FILTERS); + expect(result).toEqual({}); + }); + + it("handles missing entities array in arguments", async () => { + mockGenerateResponse.mockResolvedValueOnce({ + toolCalls: [ + { + name: "extract_entities", + arguments: JSON.stringify({ wrong_key: [] }), + }, + ], + }); + + const mg = graph(); + // args.entities is undefined → for..of on undefined throws → caught + const result = await mg._retrieveNodesFromData("anything", FILTERS); + expect(result).toEqual({}); + }); + + it("skips tool calls with unrelated names", async () => { + mockGenerateResponse.mockResolvedValueOnce({ + toolCalls: [ + { + name: "some_other_tool", + arguments: JSON.stringify({ entities: [{ entity: "X", entity_type: "Y" }] }), + }, + ], + }); + + const mg = graph(); + const result = await mg._retrieveNodesFromData("anything", FILTERS); + expect(result).toEqual({}); + }); + + it("normalises entity names to lowercase with underscores", async () => { + mockGenerateResponse.mockResolvedValueOnce({ + toolCalls: [ + { + name: "extract_entities", + arguments: JSON.stringify({ + entities: [{ entity: "New York City", entity_type: "City Name" }], + }), + }, + ], + }); + + const mg = graph(); + const result = await mg._retrieveNodesFromData("anything", FILTERS); + expect(result).toEqual({ new_york_city: "city_name" }); + }); + + it("passes json_object response format and the correct system prompt", async () => { + mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] }); + + const mg = graph(); + await mg._retrieveNodesFromData("test data", FILTERS); + + const [messages, responseFormat] = mockGenerateResponse.mock.calls[0]; + expect(responseFormat).toEqual({ type: "json_object" }); + + const systemMsg = messages[0].content as string; + expect(systemMsg.toLowerCase()).toContain("json"); + expect(systemMsg).toContain("test-user"); + }); +}); + +// ═══════════════════════════════════════════════════════════════════════════ +// 2. _establishNodesRelationsFromData – relation extraction +// ═══════════════════════════════════════════════════════════════════════════ + +describe("_establishNodesRelationsFromData", () => { + it("parses a well-formed establish_relationships tool call", async () => { + mockGenerateResponse.mockResolvedValueOnce({ + toolCalls: [ + { + name: "establish_relationships", + arguments: JSON.stringify({ + entities: [ + { source: "Alice", relationship: "likes", destination: "Pizza" }, + ], + }), + }, + ], + }); + + const mg = graph(); + const result = await mg._establishNodesRelationsFromData( + "Alice likes pizza", + FILTERS, + { alice: "person", pizza: "food" }, + ); + + expect(result).toEqual([ + { source: "alice", relationship: "likes", destination: "pizza" }, + ]); + }); + + it("returns empty array when LLM returns a string", async () => { + mockGenerateResponse.mockResolvedValueOnce("just a string"); + + const mg = graph(); + const result = await mg._establishNodesRelationsFromData("x", FILTERS, {}); + expect(result).toEqual([]); + }); + + it("returns empty array when toolCalls is empty", async () => { + mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] }); + + const mg = graph(); + const result = await mg._establishNodesRelationsFromData("x", FILTERS, {}); + expect(result).toEqual([]); + }); + + it("returns empty array when entities key is missing from arguments", async () => { + mockGenerateResponse.mockResolvedValueOnce({ + toolCalls: [ + { + name: "establish_relationships", + arguments: JSON.stringify({ not_entities: [] }), + }, + ], + }); + + const mg = graph(); + const result = await mg._establishNodesRelationsFromData("x", FILTERS, {}); + // args.entities is undefined → falls back to [] + expect(result).toEqual([]); + }); + + it("throws on malformed JSON in tool call arguments (no try/catch in source)", async () => { + mockGenerateResponse.mockResolvedValueOnce({ + toolCalls: [ + { name: "establish_relationships", arguments: "<>" }, + ], + }); + + const mg = graph(); + // _establishNodesRelationsFromData does JSON.parse without try/catch + await expect( + mg._establishNodesRelationsFromData("x", FILTERS, {}), + ).rejects.toThrow(); + }); + + it("appends JSON format suffix to system prompt (no custom prompt)", async () => { + mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] }); + + const mg = graph(); + await mg._establishNodesRelationsFromData("data", FILTERS, { a: "b" }); + + const [messages, responseFormat] = mockGenerateResponse.mock.calls[0]; + expect(responseFormat).toEqual({ type: "json_object" }); + + const systemContent = messages[0].content as string; + expect(systemContent.toLowerCase()).toContain("json"); + expect(systemContent).toContain("test-user"); + expect(systemContent).not.toContain("USER_ID"); + // CUSTOM_PROMPT placeholder stays when no custom prompt is configured + // (only replaced when config.graphStore.customPrompt is set) + }); + + it("appends JSON format suffix and custom prompt when configured", async () => { + mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] }); + + const mg = graph({ customPrompt: "Focus on food relationships only." }); + await mg._establishNodesRelationsFromData("data", FILTERS, {}); + + const [messages] = mockGenerateResponse.mock.calls[0]; + const systemContent = messages[0].content as string; + expect(systemContent.toLowerCase()).toContain("json"); + expect(systemContent).toContain("Focus on food relationships only."); + }); +}); + +// ═══════════════════════════════════════════════════════════════════════════ +// 3. _getDeleteEntitiesFromSearchOutput – deletion identification +// ═══════════════════════════════════════════════════════════════════════════ + +describe("_getDeleteEntitiesFromSearchOutput", () => { + const SEARCH_OUTPUT = [ + { + source: "alice", + source_id: "1", + relationship: "likes", + relation_id: "r1", + destination: "pizza", + destination_id: "2", + similarity: 0.95, + }, + ]; + + it("parses a well-formed delete_graph_memory tool call", async () => { + mockGenerateResponse.mockResolvedValueOnce({ + toolCalls: [ + { + name: "delete_graph_memory", + arguments: JSON.stringify({ + source: "Alice", + relationship: "likes", + destination: "Pizza", + }), + }, + ], + }); + + const mg = graph(); + const result = await mg._getDeleteEntitiesFromSearchOutput(SEARCH_OUTPUT, "Alice hates pizza", FILTERS); + + expect(result).toEqual([ + { source: "alice", relationship: "likes", destination: "pizza" }, + ]); + }); + + it("returns empty array when LLM returns a string", async () => { + mockGenerateResponse.mockResolvedValueOnce("string response"); + + const mg = graph(); + const result = await mg._getDeleteEntitiesFromSearchOutput(SEARCH_OUTPUT, "x", FILTERS); + expect(result).toEqual([]); + }); + + it("returns empty array when no tool calls are present", async () => { + mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] }); + + const mg = graph(); + const result = await mg._getDeleteEntitiesFromSearchOutput(SEARCH_OUTPUT, "x", FILTERS); + expect(result).toEqual([]); + }); + + it("skips non-delete_graph_memory tool calls", async () => { + mockGenerateResponse.mockResolvedValueOnce({ + toolCalls: [ + { + name: "noop", + arguments: JSON.stringify({}), + }, + ], + }); + + const mg = graph(); + const result = await mg._getDeleteEntitiesFromSearchOutput(SEARCH_OUTPUT, "x", FILTERS); + expect(result).toEqual([]); + }); + + it("collects multiple delete tool calls", async () => { + mockGenerateResponse.mockResolvedValueOnce({ + toolCalls: [ + { + name: "delete_graph_memory", + arguments: JSON.stringify({ source: "A", relationship: "r1", destination: "B" }), + }, + { + name: "delete_graph_memory", + arguments: JSON.stringify({ source: "C", relationship: "r2", destination: "D" }), + }, + ], + }); + + const mg = graph(); + const result = await mg._getDeleteEntitiesFromSearchOutput(SEARCH_OUTPUT, "x", FILTERS); + expect(result).toHaveLength(2); + expect(result[0].source).toBe("a"); + expect(result[1].source).toBe("c"); + }); + + it("passes json_object format and includes 'json' in system prompt", async () => { + mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] }); + + const mg = graph(); + await mg._getDeleteEntitiesFromSearchOutput(SEARCH_OUTPUT, "data", FILTERS); + + const [messages, responseFormat] = mockGenerateResponse.mock.calls[0]; + expect(responseFormat).toEqual({ type: "json_object" }); + + const systemContent = messages[0].content as string; + expect(systemContent.toLowerCase()).toContain("json"); + expect(systemContent).toContain("test-user"); + expect(systemContent).not.toContain("USER_ID"); + }); + + it("handles empty searchOutput array", async () => { + mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] }); + + const mg = graph(); + const result = await mg._getDeleteEntitiesFromSearchOutput([], "data", FILTERS); + expect(result).toEqual([]); + }); +}); + +// ═══════════════════════════════════════════════════════════════════════════ +// 4. Prompt construction — JSON keyword present in every json_object site +// ═══════════════════════════════════════════════════════════════════════════ + +describe("Prompt construction — all json_object sites include 'json'", () => { + it("_retrieveNodesFromData system message includes 'json' for any userId", async () => { + for (const userId of ["", "user-1", "special<>chars", "ユーザー"]) { + mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] }); + const mg = graph(); + await mg._retrieveNodesFromData("test", { userId }); + + const systemMsg = mockGenerateResponse.mock.calls.at(-1)![0][0].content; + expect(systemMsg.toLowerCase()).toContain("json"); + } + }); + + it("_establishNodesRelationsFromData system message includes 'json' for any userId", async () => { + for (const userId of ["", "user-1", "special<>chars"]) { + mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] }); + const mg = graph(); + await mg._establishNodesRelationsFromData("test", { userId }, {}); + + const systemMsg = mockGenerateResponse.mock.calls.at(-1)![0][0].content; + expect(systemMsg.toLowerCase()).toContain("json"); + } + }); + + it("_getDeleteEntitiesFromSearchOutput system message includes 'json' for any userId", async () => { + for (const userId of ["", "user-1", "special<>chars"]) { + mockGenerateResponse.mockResolvedValueOnce({ toolCalls: [] }); + const mg = graph(); + await mg._getDeleteEntitiesFromSearchOutput([], "test", { userId }); + + const systemMsg = mockGenerateResponse.mock.calls.at(-1)![0][0].content; + expect(systemMsg.toLowerCase()).toContain("json"); + } + }); +}); + +// ═══════════════════════════════════════════════════════════════════════════ +// 5. Edge cases – malformed entity fields in _removeSpacesFromEntities +// ═══════════════════════════════════════════════════════════════════════════ + +describe("_removeSpacesFromEntities (via _establishNodesRelationsFromData)", () => { + it("normalises spaces and case in entity source/relationship/destination", async () => { + mockGenerateResponse.mockResolvedValueOnce({ + toolCalls: [ + { + name: "establish_relationships", + arguments: JSON.stringify({ + entities: [ + { source: "New York", relationship: "Capital Of", destination: "United States" }, + ], + }), + }, + ], + }); + + const mg = graph(); + const result = await mg._establishNodesRelationsFromData("test", FILTERS, {}); + + expect(result).toEqual([ + { source: "new_york", relationship: "capital_of", destination: "united_states" }, + ]); + }); +}); diff --git a/mem0-ts/src/oss/tests/graph-prompts.test.ts b/mem0-ts/src/oss/tests/graph-prompts.test.ts new file mode 100644 index 000000000..1b0a00453 --- /dev/null +++ b/mem0-ts/src/oss/tests/graph-prompts.test.ts @@ -0,0 +1,168 @@ +import { + DELETE_RELATIONS_SYSTEM_PROMPT, + EXTRACT_RELATIONS_PROMPT, + UPDATE_GRAPH_PROMPT, + getDeleteMessages, + formatEntities, +} from "../src/graphs/utils"; + +/** + * Regression tests for graph prompts (issue #4248). + * + * When response_format: { type: "json_object" } is used, OpenAI requires + * the word "json" (case-insensitive) to appear in at least one message. + * Missing it produces a 400 error. + * + * Three call sites use json_object today: + * 1. _getDeleteEntitiesFromSearchOutput → DELETE_RELATIONS_SYSTEM_PROMPT + * 2. _retrieveNodesFromData → inline prompt (graph_memory.ts) + * 3. _getRelatedEntities → EXTRACT_RELATIONS_PROMPT + suffix + * + * See: https://github.com/mem0ai/mem0/issues/4248 + */ + +// ─── JSON keyword presence ──────────────────────────────────────────────────── + +describe("Graph prompts — JSON keyword requirement", () => { + it("DELETE_RELATIONS_SYSTEM_PROMPT contains 'json'", () => { + expect(DELETE_RELATIONS_SYSTEM_PROMPT.toLowerCase()).toContain("json"); + }); + + it("EXTRACT_RELATIONS_PROMPT produces a message containing 'json' once the suffix is appended", () => { + // graph_memory.ts appends "\nPlease provide your response in JSON format." + const withSuffix = + EXTRACT_RELATIONS_PROMPT + "\nPlease provide your response in JSON format."; + expect(withSuffix.toLowerCase()).toContain("json"); + }); + + it("getDeleteMessages system message contains 'json' after USER_ID substitution", () => { + const [systemContent] = getDeleteMessages( + "alice -- loves -- pizza", + "Alice now hates pizza", + "user-42", + ); + expect(systemContent.toLowerCase()).toContain("json"); + }); + + it("entity extraction inline prompt contains 'json' (simulated from graph_memory.ts)", () => { + // Mirrors the template in _retrieveNodesFromData() + const userId = "user-1"; + const prompt = `You are a smart assistant who understands entities and their types in a given text. If user message contains self reference such as 'I', 'me', 'my' etc. then use ${userId} as the source entity. Extract all the entities from the text. ***DO NOT*** answer the question itself if the given text is a question. Respond in JSON format.`; + expect(prompt.toLowerCase()).toContain("json"); + }); +}); + +// ─── getDeleteMessages ──────────────────────────────────────────────────────── + +describe("getDeleteMessages", () => { + it("replaces USER_ID with the provided userId in the system prompt", () => { + const [system] = getDeleteMessages("mem", "data", "alice-123"); + expect(system).toContain("alice-123"); + expect(system).not.toContain("USER_ID"); + }); + + it("includes existing memories and new data in the user prompt", () => { + const existing = "bob -- knows -- carol"; + const newData = "Bob no longer knows Carol"; + const [, user] = getDeleteMessages(existing, newData, "u1"); + expect(user).toContain(existing); + expect(user).toContain(newData); + }); + + it("returns a 2-tuple [system, user]", () => { + const result = getDeleteMessages("a", "b", "c"); + expect(result).toHaveLength(2); + expect(typeof result[0]).toBe("string"); + expect(typeof result[1]).toBe("string"); + }); + + // — Malformed / edge-case inputs — + + it("handles empty strings without throwing", () => { + expect(() => getDeleteMessages("", "", "")).not.toThrow(); + const [system, user] = getDeleteMessages("", "", ""); + expect(system.toLowerCase()).toContain("json"); + expect(typeof user).toBe("string"); + }); + + it("handles special characters in userId (e.g. angle brackets, quotes)", () => { + const [system] = getDeleteMessages("mem", "data", ''); + expect(system).toContain(''); + expect(system).not.toContain("USER_ID"); + }); + + it("handles unicode input", () => { + const [system, user] = getDeleteMessages("日本語メモリ", "新しい情報", "ユーザー1"); + expect(system).toContain("ユーザー1"); + expect(user).toContain("日本語メモリ"); + expect(user).toContain("新しい情報"); + }); + + it("handles very long input strings", () => { + const longStr = "x".repeat(100_000); + expect(() => getDeleteMessages(longStr, longStr, "u")).not.toThrow(); + const [system] = getDeleteMessages(longStr, longStr, "u"); + expect(system.toLowerCase()).toContain("json"); + }); +}); + +// ─── formatEntities ─────────────────────────────────────────────────────────── + +describe("formatEntities", () => { + it("formats a single entity triplet", () => { + const result = formatEntities([ + { source: "Alice", relationship: "knows", destination: "Bob" }, + ]); + expect(result).toBe("Alice -- knows -- Bob"); + }); + + it("joins multiple entities with newlines", () => { + const result = formatEntities([ + { source: "A", relationship: "r1", destination: "B" }, + { source: "C", relationship: "r2", destination: "D" }, + ]); + expect(result).toBe("A -- r1 -- B\nC -- r2 -- D"); + }); + + it("returns empty string for empty array", () => { + expect(formatEntities([])).toBe(""); + }); + + it("preserves special characters in entity fields", () => { + const result = formatEntities([ + { source: "O'Brien", relationship: "said \"hello\"", destination: "café" }, + ]); + expect(result).toContain("O'Brien"); + expect(result).toContain('said "hello"'); + expect(result).toContain("café"); + }); +}); + +// ─── Prompt structural invariants ───────────────────────────────────────────── + +describe("Prompt structural invariants", () => { + it("DELETE_RELATIONS_SYSTEM_PROMPT contains USER_ID placeholder", () => { + expect(DELETE_RELATIONS_SYSTEM_PROMPT).toContain("USER_ID"); + }); + + it("EXTRACT_RELATIONS_PROMPT contains USER_ID placeholder", () => { + expect(EXTRACT_RELATIONS_PROMPT).toContain("USER_ID"); + }); + + it("EXTRACT_RELATIONS_PROMPT contains CUSTOM_PROMPT placeholder", () => { + expect(EXTRACT_RELATIONS_PROMPT).toContain("CUSTOM_PROMPT"); + }); + + it("UPDATE_GRAPH_PROMPT contains memory template placeholders", () => { + expect(UPDATE_GRAPH_PROMPT).toContain("{existing_memories}"); + expect(UPDATE_GRAPH_PROMPT).toContain("{new_memories}"); + }); + + it("DELETE_RELATIONS_SYSTEM_PROMPT is non-empty and reasonably sized", () => { + expect(DELETE_RELATIONS_SYSTEM_PROMPT.length).toBeGreaterThan(100); + }); + + it("EXTRACT_RELATIONS_PROMPT is non-empty and reasonably sized", () => { + expect(EXTRACT_RELATIONS_PROMPT.length).toBeGreaterThan(100); + }); +});