feat(oss): port v3 pipeline with hybrid search, entity extraction, and additive scoring (#4805)

Co-authored-by: Soumil Rathi <soumilrathi@gmail.com>
Co-authored-by: Saket Aryan <saketaryan2002@gmail.com>
Co-authored-by: chaithanyak42 <chaithanya.kumar42a@gmail.com>
Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
This commit is contained in:
soumil-rathi
2026-04-14 18:00:58 +05:30
committed by GitHub
co-authored by Soumil Rathi Saket Aryan chaithanyak42 kartik-mem0
parent 57f944e18a
commit a488e19044
120 changed files with 10107 additions and 17135 deletions
+50 -23
View File
@@ -22,18 +22,21 @@ jest.mock("../src/llms/openai", () => ({
.fn()
.mockImplementation(
(messages: Array<{ role: string; content: string }>) => {
const hasSystemRole = messages.some((m) => m.role === "system");
if (hasSystemRole) {
return JSON.stringify({ facts: ["stored fact"] });
}
// V3 pipeline: single LLM call with additive extraction prompt.
// Extract the user input from the prompt to produce unique memories.
const userMsg = messages.find((m) => m.role === "user");
const content = userMsg?.content ?? "";
// Pull the text between "## New Messages" and the next "##"
const newMsgMatch = content.match(
/## New Messages\n([\s\S]*?)(?=\n##|$)/,
);
const extracted = newMsgMatch ? newMsgMatch[1].trim() : "stored fact";
return JSON.stringify({
memory: [
{
id: "new",
event: "ADD",
text: "stored fact",
old_memory: "",
new_memory: "stored fact",
id: "0",
text: extracted,
attributed_to: "user",
},
],
});
@@ -42,9 +45,15 @@ jest.mock("../src/llms/openai", () => ({
})),
}));
const mockEmbedding = new Array(1536).fill(0.1);
jest.mock("../src/embeddings/openai", () => ({
OpenAIEmbedder: jest.fn().mockImplementation(() => ({
embed: jest.fn().mockResolvedValue(new Array(1536).fill(0.1)),
embed: jest.fn().mockResolvedValue(mockEmbedding),
embedBatch: jest
.fn()
.mockImplementation((texts: string[]) =>
Promise.resolve(texts.map(() => mockEmbedding)),
),
embeddingDims: 1536,
})),
}));
@@ -87,7 +96,9 @@ describe("Memory - get()", () => {
});
test("returns the memory matching the ID from add()", async () => {
const addResult: SearchResult = await memory.add("I love AI", { userId });
const addResult: SearchResult = await memory.add("I love AI", {
userId,
});
const id = addResult.results[0].id;
const item: MemoryItem | null = await memory.get(id);
expect(item).not.toBeNull();
@@ -108,7 +119,9 @@ describe("Memory - get()", () => {
});
test("returns hash and createdAt on stored memory", async () => {
const addResult: SearchResult = await memory.add("Hash test", { userId });
const addResult: SearchResult = await memory.add("Hash test", {
userId,
});
const item: MemoryItem | null = await memory.get(addResult.results[0].id);
expect(typeof item!.hash).toBe("string");
expect(item!.createdAt).toBeDefined();
@@ -233,13 +246,15 @@ describe("Memory - deleteAll()", () => {
await memory.add("Fact B", { userId });
const result = await memory.deleteAll({ userId });
expect(result.message).toBe("Memories deleted successfully!");
const remaining: SearchResult = await memory.getAll({ userId });
const remaining: SearchResult = await memory.getAll({
filters: { user_id: userId },
});
expect(remaining.results).toHaveLength(0);
});
test("throws when no filter is provided", async () => {
await expect(memory.deleteAll({} as any)).rejects.toThrow(
"At least one filter is required",
"At least one filter is required to delete all memories",
);
});
});
@@ -261,13 +276,17 @@ describe("Memory - getAll()", () => {
test("returns all stored memories for the user", async () => {
await memory.add("First", { userId });
await memory.add("Second", { userId });
const result: SearchResult = await memory.getAll({ userId });
const result: SearchResult = await memory.getAll({
filters: { user_id: userId },
});
expect(Array.isArray(result.results)).toBe(true);
expect(result.results.length).toBeGreaterThanOrEqual(2);
});
test("each result has id and memory fields", async () => {
const result: SearchResult = await memory.getAll({ userId });
const result: SearchResult = await memory.getAll({
filters: { user_id: userId },
});
for (const item of result.results) {
expect(item.id).toBeDefined();
expect(typeof item.memory).toBe("string");
@@ -276,7 +295,7 @@ describe("Memory - getAll()", () => {
test("returns empty array when no memories exist", async () => {
const result: SearchResult = await memory.getAll({
userId: "no_such_user",
filters: { user_id: "no_such_user" },
});
expect(result.results).toHaveLength(0);
});
@@ -298,12 +317,16 @@ describe("Memory - search()", () => {
});
test("returns SearchResult with results array", async () => {
const result: SearchResult = await memory.search("TypeScript", { userId });
const result: SearchResult = await memory.search("TypeScript", {
filters: { user_id: userId },
});
expect(Array.isArray(result.results)).toBe(true);
});
test("returns results with score field", async () => {
const result: SearchResult = await memory.search("content", { userId });
const result: SearchResult = await memory.search("content", {
filters: { user_id: userId },
});
if (result.results.length > 0) {
expect(typeof result.results[0].score).toBe("number");
}
@@ -311,13 +334,13 @@ describe("Memory - search()", () => {
test("throws when no userId/agentId/runId provided", async () => {
await expect(memory.search("query", {} as any)).rejects.toThrow(
"One of the filters: userId, agentId or runId is required!",
"filters must contain at least one of: user_id, agent_id, run_id",
);
});
test("returns empty results for user with no memories", async () => {
const result: SearchResult = await memory.search("query", {
userId: "empty_user",
filters: { user_id: "empty_user" },
});
expect(result.results).toHaveLength(0);
});
@@ -338,14 +361,18 @@ describe("Memory - history()", () => {
});
test("records ADD event after add()", async () => {
const addResult: SearchResult = await memory.add("New fact", { userId });
const addResult: SearchResult = await memory.add("New fact", {
userId,
});
const history = await memory.history(addResult.results[0].id);
expect(Array.isArray(history)).toBe(true);
expect(history.length).toBeGreaterThan(0);
});
test("records additional entry after update()", async () => {
const addResult: SearchResult = await memory.add("Before", { userId });
const addResult: SearchResult = await memory.add("Before", {
userId,
});
const id = addResult.results[0].id;
await memory.update(id, "After");
const history = await memory.history(id);