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); }); });