refactor: update MemoryClient to use entity options instead of filters

This commit is contained in:
kartik-mem0
2026-04-14 17:13:52 +05:30
parent 28cc8fcee4
commit c3f48d093e
21 changed files with 201 additions and 249 deletions
+15 -30
View File
@@ -23,8 +23,17 @@ import { captureClientEvent, generateHash } from "./telemetry";
import { camelToSnake, camelToSnakeKeys, snakeToCamelKeys } from "./utils"; import { camelToSnake, camelToSnakeKeys, snakeToCamelKeys } from "./utils";
import { createExceptionFromResponse, MemoryError } from "../common/exceptions"; import { createExceptionFromResponse, MemoryError } from "../common/exceptions";
// Entity parameters that must be passed via filters, not top-level (snake_case only - matches API) // Entity params that must be passed via filters - check both snake_case and camelCase
const ENTITY_PARAMS = ["user_id", "agent_id", "app_id", "run_id"]; const ENTITY_PARAMS = [
"user_id",
"agent_id",
"app_id",
"run_id",
"userId",
"agentId",
"appId",
"runId",
];
/** /**
* Validates that no top-level entity parameters are passed. * Validates that no top-level entity parameters are passed.
@@ -207,13 +216,7 @@ export default class MemoryClient {
): Promise<Array<Memory>> { ): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping(); if (this.telemetryId === "") await this.ping();
// Extract filters and spread entity IDs into payload (API expects top-level entity IDs) const payload = this._preparePayload(messages, options);
const { filters, ...rest } = options;
const payload: Record<string, any> = {
messages,
...camelToSnakeKeys(rest),
...(filters && filters), // Spread filters content into payload
};
const payloadKeys = Object.keys(payload); const payloadKeys = Object.keys(payload);
this._captureEvent("add", [payloadKeys]); this._captureEvent("add", [payloadKeys]);
@@ -355,27 +358,9 @@ export default class MemoryClient {
if (this.telemetryId === "") await this.ping(); if (this.telemetryId === "") await this.ping();
const payloadKeys = Object.keys(options || {}); const payloadKeys = Object.keys(options || {});
this._captureEvent("delete_all", [payloadKeys]); this._captureEvent("delete_all", [payloadKeys]);
const snakeOptions = camelToSnakeKeys(this._prepareParams(options));
// Extract filters and build query params from filters (snake_case keys) // @ts-ignore
const { filters, ...rest } = options; const params = new URLSearchParams(snakeOptions);
const queryParams: Record<string, string> = {};
if (filters) {
// Pass filter keys directly as query params (already snake_case)
for (const [key, value] of Object.entries(filters)) {
if (value != null) {
queryParams[key] = String(value);
}
}
}
// Add any other non-filter params
const otherParams = camelToSnakeKeys(this._prepareParams(rest));
for (const [key, value] of Object.entries(otherParams)) {
if (value != null) {
queryParams[key] = String(value);
}
}
const params = new URLSearchParams(queryParams);
const response = await this._fetchWithErrorHandling( const response = await this._fetchWithErrorHandling(
`${this.host}/v1/memories/?${params}`, `${this.host}/v1/memories/?${params}`,
{ {
+10 -5
View File
@@ -1,6 +1,13 @@
// ─── Entity Options (for add/delete — top-level identity) ───
export interface EntityOptions {
userId?: string;
agentId?: string;
appId?: string;
runId?: string;
}
// ─── Per-Method Options ───────────────────────────────────── // ─── Per-Method Options ─────────────────────────────────────
export interface AddMemoryOptions { export interface AddMemoryOptions extends EntityOptions {
filters?: Record<string, any>;
metadata?: Record<string, any>; metadata?: Record<string, any>;
infer?: boolean; infer?: boolean;
customCategories?: custom_categories[]; customCategories?: custom_categories[];
@@ -28,9 +35,7 @@ export interface GetAllMemoryOptions {
categories?: string[]; categories?: string[];
} }
export interface DeleteAllMemoryOptions { export interface DeleteAllMemoryOptions extends EntityOptions {}
filters?: Record<string, any>;
}
// ─── Project Options ──────────────────────────────────────── // ─── Project Options ────────────────────────────────────────
export interface ProjectOptions { export interface ProjectOptions {
@@ -52,7 +52,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
]; ];
const result = await client.add(messages, { const result = await client.add(messages, {
filters: { user_id: TEST_USER_ID }, userId: TEST_USER_ID,
}); });
// v3 API processes memories asynchronously — returns PENDING // v3 API processes memories asynchronously — returns PENDING
@@ -73,7 +73,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
]; ];
const result = await client.add(messages, { const result = await client.add(messages, {
filters: { user_id: TEST_USER_ID }, userId: TEST_USER_ID,
}); });
expect(result).toHaveProperty("eventId"); expect(result).toHaveProperty("eventId");
}); });
@@ -208,7 +208,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
test("deleteAll for non-existent user does not throw", async () => { test("deleteAll for non-existent user does not throw", async () => {
const result = await client.deleteAll({ const result = await client.deleteAll({
filters: { user_id: `nonexistent-user-${randomUUID()}` }, userId: `nonexistent-user-${randomUUID()}`,
}); });
expect(result).toBeDefined(); expect(result).toBeDefined();
@@ -239,7 +239,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
describe("cleanup operations", () => { describe("cleanup operations", () => {
test("deletes all memories for test user", async () => { test("deletes all memories for test user", async () => {
const result = await client.deleteAll({ const result = await client.deleteAll({
filters: { user_id: TEST_USER_ID }, userId: TEST_USER_ID,
}); });
expect(result).toBeDefined(); expect(result).toBeDefined();
expect(typeof result.message).toBe("string"); expect(typeof result.message).toBe("string");
@@ -18,12 +18,10 @@ export default async function globalSetup() {
// Full project wipe — all four filters set explicitly // Full project wipe — all four filters set explicitly
try { try {
await client.deleteAll({ await client.deleteAll({
filters: { userId: "*",
user_id: "*", agentId: "*",
agent_id: "*", appId: "*",
app_id: "*", runId: "*",
run_id: "*",
},
}); });
} catch { } catch {
// ignore — may 404 if no data exists // ignore — may 404 if no data exists
@@ -17,12 +17,10 @@ export default async function globalTeardown() {
try { try {
await client.deleteAll({ await client.deleteAll({
filters: { userId: "*",
user_id: "*", agentId: "*",
agent_id: "*", appId: "*",
app_id: "*", runId: "*",
run_id: "*",
},
}); });
} catch { } catch {
// ignore // ignore
@@ -187,7 +187,7 @@ export async function cleanupTestUser(
userId: string, userId: string,
): Promise<void> { ): Promise<void> {
try { try {
await client.deleteAll({ filters: { user_id: userId } }); await client.deleteAll({ userId });
} catch { } catch {
// ignore // ignore
} }
@@ -210,12 +210,10 @@ export async function fullProjectCleanup(client: MemoryClient): Promise<void> {
// Delete all memories — all four filters set explicitly // Delete all memories — all four filters set explicitly
try { try {
await client.deleteAll({ await client.deleteAll({
filters: { userId: "*",
user_id: "*", agentId: "*",
agent_id: "*", appId: "*",
app_id: "*", runId: "*",
run_id: "*",
},
}); });
} catch { } catch {
// ignore — may 404 if no data exists // ignore — may 404 if no data exists
@@ -27,9 +27,7 @@ describe("MemoryClient - add()", () => {
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY }); const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.add([{ role: "user", content: "Hello" }], { await client.add([{ role: "user", content: "Hello" }], { userId: "u1" });
filters: { user_id: "u1" },
});
expect(findFetchCall(mock, "/v3/memories/", "POST")).toBeDefined(); expect(findFetchCall(mock, "/v3/memories/", "POST")).toBeDefined();
}); });
@@ -41,26 +39,24 @@ describe("MemoryClient - add()", () => {
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY }); const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.add(messages, { filters: { user_id: "u1" } }); await client.add(messages, { userId: "u1" });
const call = findFetchCall(mock, "/v3/memories/", "POST"); const call = findFetchCall(mock, "/v3/memories/", "POST");
expect(getFetchBody(call!).messages).toEqual(messages); expect(getFetchBody(call!).messages).toEqual(messages);
}); });
test("spreads filters into top-level request body (API expects flat entity IDs)", async () => { test("includes user_id in request body", async () => {
const extra = new Map<string, { status: number; body: unknown }>(); const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v3/memories/", { status: 200, body: [createMockMemory()] }); extra.set("/v3/memories/", { status: 200, body: [createMockMemory()] });
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY }); const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.add([{ role: "user", content: "test" }], { await client.add([{ role: "user", content: "test" }], {
filters: { user_id: "user_1" }, user_id: "user_1",
}); });
const call = findFetchCall(mock, "/v3/memories/", "POST"); const call = findFetchCall(mock, "/v3/memories/", "POST");
const body = getFetchBody(call!) as { user_id: string }; expect(getFetchBody(call!).user_id).toBe("user_1");
// Entity IDs are spread into top level, not nested under filters
expect(body.user_id).toBe("user_1");
}); });
test("sends empty messages array without crashing", async () => { test("sends empty messages array without crashing", async () => {
@@ -69,7 +65,7 @@ describe("MemoryClient - add()", () => {
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY }); const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.add([], { filters: { user_id: "u1" } }); await client.add([], { userId: "u1" });
const call = findFetchCall(mock, "/v3/memories/", "POST"); const call = findFetchCall(mock, "/v3/memories/", "POST");
expect(getFetchBody(call!).messages).toEqual([]); expect(getFetchBody(call!).messages).toEqual([]);
@@ -219,7 +215,7 @@ describe("MemoryClient - deleteAll()", () => {
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY }); const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.deleteAll({ filters: { user_id: "u1" } }); await client.deleteAll({ userId: "u1" });
const call = mock.mock.calls.find( const call = mock.mock.calls.find(
(c: [string, RequestInit]) => (c: [string, RequestInit]) =>
@@ -235,7 +231,7 @@ describe("MemoryClient - deleteAll()", () => {
const mock = setupMockFetch(extra); const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY }); const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.deleteAll({ filters: { user_id: "user@email.com" } }); await client.deleteAll({ userId: "user@email.com" });
const call = mock.mock.calls.find( const call = mock.mock.calls.find(
(c: [string, RequestInit]) => (c: [string, RequestInit]) =>
+3 -3
View File
@@ -30,7 +30,7 @@ async function runTests(memory: Memory) {
const result1 = await memory.add( const result1 = await memory.add(
"Hi, my name is John and I am a software engineer.", "Hi, my name is John and I am a software engineer.",
{ {
filters: { user_id: "john" }, userId: "john",
}, },
); );
console.log("Added memory:", result1); console.log("Added memory:", result1);
@@ -43,7 +43,7 @@ async function runTests(memory: Memory) {
{ role: "assistant", content: "I love Paris, it is my favorite city." }, { role: "assistant", content: "I love Paris, it is my favorite city." },
], ],
{ {
filters: { user_id: "john" }, userId: "john",
}, },
); );
console.log("Added messages:", result2); console.log("Added messages:", result2);
@@ -58,7 +58,7 @@ async function runTests(memory: Memory) {
}, },
], ],
{ {
filters: { user_id: "john" }, userId: "john",
}, },
); );
console.log("Updated messages:", result3); console.log("Updated messages:", result3);
+1 -1
View File
@@ -52,7 +52,7 @@ ${memoriesStr}`;
const assistantResponse = response.message.content || ""; const assistantResponse = response.message.content || "";
messages.push({ role: "assistant", content: assistantResponse }); messages.push({ role: "assistant", content: assistantResponse });
await memory.add(messages, { filters: { user_id: userId } }); await memory.add(messages, { userId: userId });
return assistantResponse; return assistantResponse;
} }
+3 -3
View File
@@ -12,7 +12,7 @@ export async function runTests(memory: Memory) {
const result1 = await memory.add( const result1 = await memory.add(
"Hi, my name is John and I am a software engineer.", "Hi, my name is John and I am a software engineer.",
{ {
filters: { user_id: "john" }, userId: "john",
}, },
); );
console.log("Added memory:", result1); console.log("Added memory:", result1);
@@ -25,7 +25,7 @@ export async function runTests(memory: Memory) {
{ role: "assistant", content: "I love Paris, it is my favorite city." }, { role: "assistant", content: "I love Paris, it is my favorite city." },
], ],
{ {
filters: { user_id: "john" }, userId: "john",
}, },
); );
console.log("Added messages:", result2); console.log("Added messages:", result2);
@@ -40,7 +40,7 @@ export async function runTests(memory: Memory) {
}, },
], ],
{ {
filters: { user_id: "john" }, userId: "john",
}, },
); );
console.log("Updated messages:", result3); console.log("Updated messages:", result3);
+36 -20
View File
@@ -53,8 +53,15 @@ import {
ScoredResult, ScoredResult,
} from "../utils/scoring"; } from "../utils/scoring";
// Entity parameters that must be passed via filters, not top-level (snake_case only - matches API) // Entity params that must be passed via filters - check both snake_case and camelCase
const ENTITY_PARAMS = ["user_id", "agent_id", "run_id"]; const ENTITY_PARAMS = [
"user_id",
"agent_id",
"run_id",
"userId",
"agentId",
"runId",
];
/** /**
* Validates that no top-level entity parameters are passed in config. * Validates that no top-level entity parameters are passed in config.
@@ -70,7 +77,7 @@ function rejectTopLevelEntityParams(
if (invalidKeys.length > 0) { if (invalidKeys.length > 0) {
throw new Error( throw new Error(
`Top-level entity parameters [${invalidKeys.join(", ")}] are not supported in ${methodName}(). ` + `Top-level entity parameters [${invalidKeys.join(", ")}] are not supported in ${methodName}(). ` +
`Use filters: { user_id: "..." } instead.`, `Use filters: { userId: "..." } instead.`,
); );
} }
} }
@@ -276,21 +283,26 @@ export class Memory {
has_filters: !!config.filters, has_filters: !!config.filters,
infer: config.infer, infer: config.infer,
}); });
const { metadata = {}, filters = {}, infer = true } = config; const {
userId,
agentId,
runId,
metadata = {},
filters = {},
infer = true,
} = config;
// Convert camelCase entity params to snake_case for storage (matches API and search/getAll filters)
if (userId) filters.user_id = metadata.user_id = userId;
if (agentId) filters.agent_id = metadata.agent_id = agentId;
if (runId) filters.run_id = metadata.run_id = runId;
// Validate filters contains at least one entity ID (snake_case)
if (!filters.user_id && !filters.agent_id && !filters.run_id) { if (!filters.user_id && !filters.agent_id && !filters.run_id) {
throw new Error( throw new Error(
"filters must contain at least one of: user_id, agent_id, run_id. " + "One of the filters: userId, agentId or runId is required!",
"Example: { filters: { user_id: 'u1' } }",
); );
} }
// Copy entity IDs to metadata for storage
if (filters.user_id) metadata.user_id = filters.user_id;
if (filters.agent_id) metadata.agent_id = filters.agent_id;
if (filters.run_id) metadata.run_id = filters.run_id;
const parsedMessages = Array.isArray(messages) const parsedMessages = Array.isArray(messages)
? (messages as Message[]) ? (messages as Message[])
: [{ role: "user", content: messages }]; : [{ role: "user", content: messages }];
@@ -1017,18 +1029,22 @@ export class Memory {
config: DeleteAllMemoryOptions, config: DeleteAllMemoryOptions,
): Promise<{ message: string }> { ): Promise<{ message: string }> {
await this._ensureInitialized(); await this._ensureInitialized();
const { filters = {} } = config;
await this._captureEvent("delete_all", { await this._captureEvent("delete_all", {
has_user_id: !!filters.user_id, has_user_id: !!config.userId,
has_agent_id: !!filters.agent_id, has_agent_id: !!config.agentId,
has_run_id: !!filters.run_id, has_run_id: !!config.runId,
}); });
const { userId, agentId, runId } = config;
if (!filters.user_id && !filters.agent_id && !filters.run_id) { // Convert camelCase entity params to snake_case for filters (matches storage and search/getAll)
const filters: SearchFilters = {};
if (userId) filters.user_id = userId;
if (agentId) filters.agent_id = agentId;
if (runId) filters.run_id = runId;
if (!Object.keys(filters).length) {
throw new Error( throw new Error(
"filters must contain at least one of: user_id, agent_id, run_id. " + "At least one filter is required to delete all memories. If you want to delete all memories, use the `reset()` method.",
"If you want to delete all memories, use the `reset()` method.",
); );
} }
+8 -4
View File
@@ -1,7 +1,13 @@
import { Message } from "../types"; import { Message } from "../types";
import { SearchFilters } from "../types"; import { SearchFilters } from "../types";
export interface AddMemoryOptions { export interface Entity {
userId?: string;
agentId?: string;
runId?: string;
}
export interface AddMemoryOptions extends Entity {
metadata?: Record<string, any>; metadata?: Record<string, any>;
filters?: SearchFilters; filters?: SearchFilters;
infer?: boolean; infer?: boolean;
@@ -18,6 +24,4 @@ export interface GetAllMemoryOptions {
filters?: SearchFilters; filters?: SearchFilters;
} }
export interface DeleteAllMemoryOptions { export interface DeleteAllMemoryOptions extends Entity {}
filters?: SearchFilters;
}
+1 -1
View File
@@ -589,7 +589,7 @@ describe("Memory – LM Studio end-to-end flow", () => {
disableHistory: true, disableHistory: true,
}); });
await mem.add("I love sushi", { filters: { user_id: "u1" } }); await mem.add("I love sushi", { userId: "u1" });
expect(mockLlm.generateResponse).toHaveBeenCalled(); expect(mockLlm.generateResponse).toHaveBeenCalled();
expect(mockEmbedder.embed).toHaveBeenCalled(); expect(mockEmbedder.embed).toHaveBeenCalled();
+13 -17
View File
@@ -96,7 +96,7 @@ describe("Memory - add()", () => {
test("returns SearchResult with results array for string input", async () => { test("returns SearchResult with results array for string input", async () => {
const result: SearchResult = await memory.add("I am a software engineer", { const result: SearchResult = await memory.add("I am a software engineer", {
filters: { user_id: userId }, userId,
}); });
expect(Array.isArray(result.results)).toBe(true); expect(Array.isArray(result.results)).toBe(true);
}); });
@@ -104,7 +104,7 @@ describe("Memory - add()", () => {
test("returns at least one result with an id", async () => { test("returns at least one result with an id", async () => {
const result: SearchResult = await memory.add( const result: SearchResult = await memory.add(
"I enjoy hiking in the mountains", "I enjoy hiking in the mountains",
{ filters: { user_id: userId } }, { userId },
); );
expect(result.results.length).toBeGreaterThan(0); expect(result.results.length).toBeGreaterThan(0);
expect(result.results[0].id).toBeDefined(); expect(result.results[0].id).toBeDefined();
@@ -112,7 +112,7 @@ describe("Memory - add()", () => {
test("result item has a memory string field", async () => { test("result item has a memory string field", async () => {
const result: SearchResult = await memory.add("My favorite color is blue", { const result: SearchResult = await memory.add("My favorite color is blue", {
filters: { user_id: userId }, userId,
}); });
expect(typeof result.results[0].memory).toBe("string"); expect(typeof result.results[0].memory).toBe("string");
}); });
@@ -122,35 +122,31 @@ describe("Memory - add()", () => {
{ role: "user", content: "What is your favorite city?" }, { role: "user", content: "What is your favorite city?" },
{ role: "assistant", content: "I love Paris." }, { role: "assistant", content: "I love Paris." },
]; ];
const result: SearchResult = await memory.add(messages, { const result: SearchResult = await memory.add(messages, { userId });
filters: { user_id: userId },
});
expect(result.results.length).toBeGreaterThan(0); expect(result.results.length).toBeGreaterThan(0);
}); });
test("works with agent_id filter instead of user_id", async () => { test("works with agentId instead of userId", async () => {
const result: SearchResult = await memory.add("test", { const result: SearchResult = await memory.add("test", {
filters: { agent_id: "agent_1" }, agentId: "agent_1",
}); });
expect(result.results.length).toBeGreaterThan(0); expect(result.results.length).toBeGreaterThan(0);
}); });
test("works with run_id filter instead of user_id", async () => { test("works with runId instead of userId", async () => {
const result: SearchResult = await memory.add("test", { const result: SearchResult = await memory.add("test", { runId: "run_1" });
filters: { run_id: "run_1" },
});
expect(result.results.length).toBeGreaterThan(0); expect(result.results.length).toBeGreaterThan(0);
}); });
test("throws when no user_id/agent_id/run_id provided in filters", async () => { test("throws when no userId/agentId/runId provided", async () => {
await expect(memory.add("test", {} as any)).rejects.toThrow( await expect(memory.add("test", {} as any)).rejects.toThrow(
"filters must contain at least one of: user_id, agent_id, run_id", "One of the filters: userId, agentId or runId is required!",
); );
}); });
test("passes metadata through to stored memory", async () => { test("passes metadata through to stored memory", async () => {
const result: SearchResult = await memory.add("I love TypeScript", { const result: SearchResult = await memory.add("I love TypeScript", {
filters: { user_id: userId }, userId,
metadata: { source: "chat", tag: "programming" }, metadata: { source: "chat", tag: "programming" },
}); });
const stored: MemoryItem | null = await memory.get(result.results[0].id); const stored: MemoryItem | null = await memory.get(result.results[0].id);
@@ -162,7 +158,7 @@ describe("Memory - add()", () => {
test("with infer=false skips LLM and stores messages directly", async () => { test("with infer=false skips LLM and stores messages directly", async () => {
const result: SearchResult = await memory.add("Direct storage content", { const result: SearchResult = await memory.add("Direct storage content", {
filters: { user_id: userId }, userId,
infer: false, infer: false,
}); });
expect(result.results.length).toBeGreaterThan(0); expect(result.results.length).toBeGreaterThan(0);
@@ -172,7 +168,7 @@ describe("Memory - add()", () => {
test("with infer=false marks event as ADD in metadata", async () => { test("with infer=false marks event as ADD in metadata", async () => {
const result: SearchResult = await memory.add("Direct fact", { const result: SearchResult = await memory.add("Direct fact", {
filters: { user_id: userId }, userId,
infer: false, infer: false,
}); });
expect(result.results[0].metadata).toEqual( expect(result.results[0].metadata).toEqual(
+18 -18
View File
@@ -97,7 +97,7 @@ describe("Memory - get()", () => {
test("returns the memory matching the ID from add()", async () => { test("returns the memory matching the ID from add()", async () => {
const addResult: SearchResult = await memory.add("I love AI", { const addResult: SearchResult = await memory.add("I love AI", {
filters: { user_id: userId }, userId,
}); });
const id = addResult.results[0].id; const id = addResult.results[0].id;
const item: MemoryItem | null = await memory.get(id); const item: MemoryItem | null = await memory.get(id);
@@ -107,7 +107,7 @@ describe("Memory - get()", () => {
test("returns a string for the memory field", async () => { test("returns a string for the memory field", async () => {
const addResult: SearchResult = await memory.add("Testing get", { const addResult: SearchResult = await memory.add("Testing get", {
filters: { user_id: userId }, userId,
}); });
const item: MemoryItem | null = await memory.get(addResult.results[0].id); const item: MemoryItem | null = await memory.get(addResult.results[0].id);
expect(typeof item!.memory).toBe("string"); expect(typeof item!.memory).toBe("string");
@@ -120,7 +120,7 @@ describe("Memory - get()", () => {
test("returns hash and createdAt on stored memory", async () => { test("returns hash and createdAt on stored memory", async () => {
const addResult: SearchResult = await memory.add("Hash test", { const addResult: SearchResult = await memory.add("Hash test", {
filters: { user_id: userId }, userId,
}); });
const item: MemoryItem | null = await memory.get(addResult.results[0].id); const item: MemoryItem | null = await memory.get(addResult.results[0].id);
expect(typeof item!.hash).toBe("string"); expect(typeof item!.hash).toBe("string");
@@ -146,7 +146,7 @@ describe("Memory - update()", () => {
// Use infer: false for update tests — bypasses LLM, gives us a stable ID // Use infer: false for update tests — bypasses LLM, gives us a stable ID
test("returns success message", async () => { test("returns success message", async () => {
const addResult: SearchResult = await memory.add("Original", { const addResult: SearchResult = await memory.add("Original", {
filters: { user_id: userId }, userId,
infer: false, infer: false,
}); });
const id = addResult.results[0].id; const id = addResult.results[0].id;
@@ -156,7 +156,7 @@ describe("Memory - update()", () => {
test("persists the updated text", async () => { test("persists the updated text", async () => {
const addResult: SearchResult = await memory.add("Before update", { const addResult: SearchResult = await memory.add("Before update", {
filters: { user_id: userId }, userId,
infer: false, infer: false,
}); });
const id = addResult.results[0].id; const id = addResult.results[0].id;
@@ -167,7 +167,7 @@ describe("Memory - update()", () => {
test("preserves createdAt and sets updatedAt", async () => { test("preserves createdAt and sets updatedAt", async () => {
const addResult: SearchResult = await memory.add("Timestamp test", { const addResult: SearchResult = await memory.add("Timestamp test", {
filters: { user_id: userId }, userId,
infer: false, infer: false,
}); });
const id = addResult.results[0].id; const id = addResult.results[0].id;
@@ -182,7 +182,7 @@ describe("Memory - update()", () => {
test("updates the hash", async () => { test("updates the hash", async () => {
const addResult: SearchResult = await memory.add("Hash change", { const addResult: SearchResult = await memory.add("Hash change", {
filters: { user_id: userId }, userId,
infer: false, infer: false,
}); });
const id = addResult.results[0].id; const id = addResult.results[0].id;
@@ -209,7 +209,7 @@ describe("Memory - delete()", () => {
test("returns success message", async () => { test("returns success message", async () => {
const addResult: SearchResult = await memory.add("Delete me", { const addResult: SearchResult = await memory.add("Delete me", {
filters: { user_id: userId }, userId,
infer: false, infer: false,
}); });
const result = await memory.delete(addResult.results[0].id); const result = await memory.delete(addResult.results[0].id);
@@ -218,7 +218,7 @@ describe("Memory - delete()", () => {
test("get() returns null after deletion", async () => { test("get() returns null after deletion", async () => {
const addResult: SearchResult = await memory.add("Temporary", { const addResult: SearchResult = await memory.add("Temporary", {
filters: { user_id: userId }, userId,
infer: false, infer: false,
}); });
const id = addResult.results[0].id; const id = addResult.results[0].id;
@@ -242,9 +242,9 @@ describe("Memory - deleteAll()", () => {
}); });
test("removes all memories for the user and returns success", async () => { test("removes all memories for the user and returns success", async () => {
await memory.add("Fact A", { filters: { user_id: userId } }); await memory.add("Fact A", { userId });
await memory.add("Fact B", { filters: { user_id: userId } }); await memory.add("Fact B", { userId });
const result = await memory.deleteAll({ filters: { user_id: userId } }); const result = await memory.deleteAll({ userId });
expect(result.message).toBe("Memories deleted successfully!"); expect(result.message).toBe("Memories deleted successfully!");
const remaining: SearchResult = await memory.getAll({ const remaining: SearchResult = await memory.getAll({
filters: { user_id: userId }, filters: { user_id: userId },
@@ -254,7 +254,7 @@ describe("Memory - deleteAll()", () => {
test("throws when no filter is provided", async () => { test("throws when no filter is provided", async () => {
await expect(memory.deleteAll({} as any)).rejects.toThrow( await expect(memory.deleteAll({} as any)).rejects.toThrow(
"filters must contain at least one of: user_id, agent_id, run_id", "At least one filter is required to delete all memories",
); );
}); });
}); });
@@ -274,8 +274,8 @@ describe("Memory - getAll()", () => {
}); });
test("returns all stored memories for the user", async () => { test("returns all stored memories for the user", async () => {
await memory.add("First", { filters: { user_id: userId } }); await memory.add("First", { userId });
await memory.add("Second", { filters: { user_id: userId } }); await memory.add("Second", { userId });
const result: SearchResult = await memory.getAll({ const result: SearchResult = await memory.getAll({
filters: { user_id: userId }, filters: { user_id: userId },
}); });
@@ -309,7 +309,7 @@ describe("Memory - search()", () => {
beforeAll(async () => { beforeAll(async () => {
memory = createMemory(); memory = createMemory();
await memory.add("I love TypeScript", { filters: { user_id: userId } }); await memory.add("I love TypeScript", { userId });
}); });
afterAll(async () => { afterAll(async () => {
@@ -362,7 +362,7 @@ describe("Memory - history()", () => {
test("records ADD event after add()", async () => { test("records ADD event after add()", async () => {
const addResult: SearchResult = await memory.add("New fact", { const addResult: SearchResult = await memory.add("New fact", {
filters: { user_id: userId }, userId,
}); });
const history = await memory.history(addResult.results[0].id); const history = await memory.history(addResult.results[0].id);
expect(Array.isArray(history)).toBe(true); expect(Array.isArray(history)).toBe(true);
@@ -371,7 +371,7 @@ describe("Memory - history()", () => {
test("records additional entry after update()", async () => { test("records additional entry after update()", async () => {
const addResult: SearchResult = await memory.add("Before", { const addResult: SearchResult = await memory.add("Before", {
filters: { user_id: userId }, userId,
}); });
const id = addResult.results[0].id; const id = addResult.results[0].id;
await memory.update(id, "After"); await memory.update(id, "After");
+1 -1
View File
@@ -118,7 +118,7 @@ describe("Memory - reset()", () => {
const mem = createMemory(); const mem = createMemory();
const userId = `reset_test_${Date.now()}`; const userId = `reset_test_${Date.now()}`;
await mem.add("Remember this fact", { filters: { user_id: userId } }); await mem.add("Remember this fact", { userId });
const before: SearchResult = await mem.getAll({ const before: SearchResult = await mem.getAll({
filters: { user_id: userId }, filters: { user_id: userId },
}); });
@@ -1070,9 +1070,7 @@ describe("Memory class – backward compat with all providers", () => {
expect(deleteResult.message).toBe("Memory deleted successfully!"); expect(deleteResult.message).toBe("Memory deleted successfully!");
// deleteAll // deleteAll
const deleteAllResult = await mem.deleteAll({ const deleteAllResult = await mem.deleteAll({ userId: "u1" });
filters: { user_id: "u1" },
});
expect(deleteAllResult.message).toBe("Memories deleted successfully!"); expect(deleteAllResult.message).toBe("Memories deleted successfully!");
// history // history
+9 -32
View File
@@ -142,7 +142,8 @@ class MemoryClient:
or a string. If a string is provided, it will be converted to or a string. If a string is provided, it will be converted to
a user message. a user message.
options: Typed options for the add operation (AddMemoryOptions). options: Typed options for the add operation (AddMemoryOptions).
**kwargs: Additional parameters such as metadata, filters. **kwargs: Additional parameters such as user_id, agent_id, app_id,
metadata, filters.
Returns: Returns:
A dictionary containing the API response in v1.1 format. A dictionary containing the API response in v1.1 format.
@@ -165,11 +166,7 @@ class MemoryClient:
raise ValueError(f"messages must be str, dict, or list[dict], got {type(messages).__name__}") raise ValueError(f"messages must be str, dict, or list[dict], got {type(messages).__name__}")
kwargs = self._prepare_params(kwargs) kwargs = self._prepare_params(kwargs)
# Extract filters and spread entity IDs into payload (API expects top-level entity IDs)
filters = kwargs.pop("filters", None)
payload = self._prepare_payload(messages, kwargs) payload = self._prepare_payload(messages, kwargs)
if filters:
payload.update(filters)
response = self.client.post("/v3/memories/", json=payload) response = self.client.post("/v3/memories/", json=payload)
response.raise_for_status() response.raise_for_status()
if "metadata" in kwargs: if "metadata" in kwargs:
@@ -370,7 +367,8 @@ class MemoryClient:
Args: Args:
options: Typed options for the delete_all operation (DeleteAllMemoryOptions). options: Typed options for the delete_all operation (DeleteAllMemoryOptions).
**kwargs: Optional parameters for filtering (filters). **kwargs: Optional parameters for filtering (user_id, agent_id,
app_id).
Returns: Returns:
A dictionary containing the API response. A dictionary containing the API response.
@@ -385,16 +383,7 @@ class MemoryClient:
""" """
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs} kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
params = self._prepare_params(kwargs) params = self._prepare_params(kwargs)
response = self.client.delete("/v1/memories/", params=params)
# Extract filters and pass as query params (snake_case keys)
query_params = {}
if "filters" in params:
filters = params.pop("filters")
if isinstance(filters, dict):
query_params.update(filters)
query_params.update(params)
response = self.client.delete("/v1/memories/", params=query_params)
response.raise_for_status() response.raise_for_status()
capture_client_event( capture_client_event(
"client.delete_all", "client.delete_all",
@@ -1081,7 +1070,8 @@ class AsyncMemoryClient:
or a string. If a string is provided, it will be converted to or a string. If a string is provided, it will be converted to
a user message. a user message.
options: Typed options for the add operation (AddMemoryOptions). options: Typed options for the add operation (AddMemoryOptions).
**kwargs: Additional parameters such as metadata, filters. **kwargs: Additional parameters such as user_id, agent_id, app_id,
metadata, filters.
Returns: Returns:
A dictionary containing the API response in v1.1 format. A dictionary containing the API response in v1.1 format.
@@ -1104,11 +1094,7 @@ class AsyncMemoryClient:
raise ValueError(f"messages must be str, dict, or list[dict], got {type(messages).__name__}") raise ValueError(f"messages must be str, dict, or list[dict], got {type(messages).__name__}")
kwargs = self._prepare_params(kwargs) kwargs = self._prepare_params(kwargs)
# Extract filters and spread entity IDs into payload (API expects top-level entity IDs)
filters = kwargs.pop("filters", None)
payload = self._prepare_payload(messages, kwargs) payload = self._prepare_payload(messages, kwargs)
if filters:
payload.update(filters)
response = await self.async_client.post("/v3/memories/", json=payload) response = await self.async_client.post("/v3/memories/", json=payload)
response.raise_for_status() response.raise_for_status()
if "metadata" in kwargs: if "metadata" in kwargs:
@@ -1293,7 +1279,7 @@ class AsyncMemoryClient:
Args: Args:
options: Typed options for the delete_all operation (DeleteAllMemoryOptions). options: Typed options for the delete_all operation (DeleteAllMemoryOptions).
**kwargs: Optional parameters for filtering (filters). **kwargs: Optional parameters for filtering (user_id, agent_id, app_id).
Returns: Returns:
A dictionary containing the API response. A dictionary containing the API response.
@@ -1308,16 +1294,7 @@ class AsyncMemoryClient:
""" """
kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs} kwargs = {**(options.model_dump(exclude_unset=True) if options else {}), **kwargs}
params = self._prepare_params(kwargs) params = self._prepare_params(kwargs)
response = await self.async_client.delete("/v1/memories/", params=params)
# Extract filters and pass as query params (snake_case keys)
query_params = {}
if "filters" in params:
filters = params.pop("filters")
if isinstance(filters, dict):
query_params.update(filters)
query_params.update(params)
response = await self.async_client.delete("/v1/memories/", params=query_params)
response.raise_for_status() response.raise_for_status()
capture_client_event("client.delete_all", self, {"keys": list(kwargs.keys()), "sync_type": "async"}) capture_client_event("client.delete_all", self, {"keys": list(kwargs.keys()), "sync_type": "async"})
return response.json() return response.json()
+45 -61
View File
@@ -425,27 +425,26 @@ class Memory(MemoryBase):
self, self,
messages, messages,
*, *,
filters: Optional[Dict[str, Any]] = None, user_id: Optional[str] = None,
agent_id: Optional[str] = None,
run_id: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None, metadata: Optional[Dict[str, Any]] = None,
infer: bool = True, infer: bool = True,
memory_type: Optional[str] = None, memory_type: Optional[str] = None,
prompt: Optional[str] = None, prompt: Optional[str] = None,
**kwargs,
): ):
""" """
Create a new memory. Create a new memory.
Adds new memories scoped to a single session id (e.g. `user_id`, `agent_id`, or `run_id`). One of those ids is required in filters. Adds new memories scoped to a single session id (e.g. `user_id`, `agent_id`, or `run_id`). One of those ids is required.
Args: Args:
messages (str or List[Dict[str, str]]): The message content or list of messages messages (str or List[Dict[str, str]]): The message content or list of messages
(e.g., `[{"role": "user", "content": "Hello"}, {"role": "assistant", "content": "Hi"}]`) (e.g., `[{"role": "user", "content": "Hello"}, {"role": "assistant", "content": "Hi"}]`)
to be processed and stored. to be processed and stored.
filters (dict, optional): Dictionary containing entity identifiers. Must contain at least one of: user_id (str, optional): ID of the user creating the memory. Defaults to None.
- user_id: ID of the user creating the memory agent_id (str, optional): ID of the agent creating the memory. Defaults to None.
- agent_id: ID of the agent creating the memory run_id (str, optional): ID of the run creating the memory. Defaults to None.
- run_id: ID of the run creating the memory
Example: `{"user_id": "user123"}` or `{"agent_id": "agent456"}`
metadata (dict, optional): Metadata to store with the memory. Defaults to None. metadata (dict, optional): Metadata to store with the memory. Defaults to None.
infer (bool, optional): If True (default), an LLM is used to extract key facts from infer (bool, optional): If True (default), an LLM is used to extract key facts from
'messages' and decide whether to add, update, or delete related memories. 'messages' and decide whether to add, update, or delete related memories.
@@ -469,19 +468,11 @@ class Memory(MemoryBase):
LLMError: If LLM operations fail. LLMError: If LLM operations fail.
DatabaseError: If database operations fail. DatabaseError: If database operations fail.
""" """
filters = filters or {}
# Validate filters contains at least one entity ID
if not any(key in filters for key in ("user_id", "agent_id", "run_id")):
raise ValueError(
"filters must contain at least one of: user_id, agent_id, run_id. "
"Example: filters={'user_id': 'u1'}"
)
processed_metadata, effective_filters = _build_filters_and_metadata( processed_metadata, effective_filters = _build_filters_and_metadata(
user_id=filters.get("user_id"), user_id=user_id,
agent_id=filters.get("agent_id"), agent_id=agent_id,
run_id=filters.get("run_id"), run_id=run_id,
input_metadata=metadata, input_metadata=metadata,
) )
@@ -507,7 +498,7 @@ class Memory(MemoryBase):
suggestion="Convert your input to a string, dictionary, or list of dictionaries." suggestion="Convert your input to a string, dictionary, or list of dictionaries."
) )
if filters.get("agent_id") is not None and memory_type == MemoryType.PROCEDURAL.value: if agent_id is not None and memory_type == MemoryType.PROCEDURAL.value:
results = self._create_procedural_memory(messages, metadata=processed_metadata, prompt=prompt) results = self._create_procedural_memory(messages, metadata=processed_metadata, prompt=prompt)
return results return results
@@ -1360,23 +1351,26 @@ class Memory(MemoryBase):
self._delete_memory(memory_id, existing_memory) self._delete_memory(memory_id, existing_memory)
return {"message": "Memory deleted successfully!"} return {"message": "Memory deleted successfully!"}
def delete_all(self, *, filters: Optional[Dict[str, Any]] = None, **kwargs): def delete_all(self, user_id: Optional[str] = None, agent_id: Optional[str] = None, run_id: Optional[str] = None):
""" """
Delete all memories. Delete all memories.
Args: Args:
filters (dict, optional): Dictionary containing entity identifiers. Must contain at least one of: user_id (str, optional): ID of the user to delete memories for. Defaults to None.
- user_id: ID of the user to delete memories for agent_id (str, optional): ID of the agent to delete memories for. Defaults to None.
- agent_id: ID of the agent to delete memories for run_id (str, optional): ID of the run to delete memories for. Defaults to None.
- run_id: ID of the run to delete memories for
Example: `{"user_id": "user123"}`
""" """
filters = filters or {} filters: Dict[str, Any] = {}
if user_id:
filters["user_id"] = user_id
if agent_id:
filters["agent_id"] = agent_id
if run_id:
filters["run_id"] = run_id
if not any(key in filters for key in ("user_id", "agent_id", "run_id")): if not filters:
raise ValueError( raise ValueError(
"filters must contain at least one of: user_id, agent_id, run_id. " "At least one filter is required to delete all memories. If you want to delete all memories, use the `reset()` method."
"If you want to delete all memories, use the `reset()` method."
) )
keys, encoded_ids = process_telemetry_filters(filters) keys, encoded_ids = process_telemetry_filters(filters)
@@ -1694,24 +1688,23 @@ class AsyncMemory(MemoryBase):
self, self,
messages, messages,
*, *,
filters: Optional[Dict[str, Any]] = None, user_id: Optional[str] = None,
agent_id: Optional[str] = None,
run_id: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None, metadata: Optional[Dict[str, Any]] = None,
infer: bool = True, infer: bool = True,
memory_type: Optional[str] = None, memory_type: Optional[str] = None,
prompt: Optional[str] = None, prompt: Optional[str] = None,
llm=None, llm=None,
**kwargs,
): ):
""" """
Create a new memory asynchronously. Create a new memory asynchronously.
Args: Args:
messages (str or List[Dict[str, str]]): Messages to store in the memory. messages (str or List[Dict[str, str]]): Messages to store in the memory.
filters (dict, optional): Dictionary containing entity identifiers. Must contain at least one of: user_id (str, optional): ID of the user creating the memory.
- user_id: ID of the user creating the memory agent_id (str, optional): ID of the agent creating the memory. Defaults to None.
- agent_id: ID of the agent creating the memory run_id (str, optional): ID of the run creating the memory. Defaults to None.
- run_id: ID of the run creating the memory
Example: `{"user_id": "user123"}` or `{"agent_id": "agent456"}`
metadata (dict, optional): Metadata to store with the memory. Defaults to None. metadata (dict, optional): Metadata to store with the memory. Defaults to None.
infer (bool, optional): Whether to infer the memories. Defaults to True. infer (bool, optional): Whether to infer the memories. Defaults to True.
memory_type (str, optional): Type of memory to create. Defaults to None. memory_type (str, optional): Type of memory to create. Defaults to None.
@@ -1721,20 +1714,8 @@ class AsyncMemory(MemoryBase):
Returns: Returns:
dict: A dictionary containing the result of the memory addition operation. dict: A dictionary containing the result of the memory addition operation.
""" """
filters = filters or {}
# Validate filters contains at least one entity ID
if not any(key in filters for key in ("user_id", "agent_id", "run_id")):
raise ValueError(
"filters must contain at least one of: user_id, agent_id, run_id. "
"Example: filters={'user_id': 'u1'}"
)
processed_metadata, effective_filters = _build_filters_and_metadata( processed_metadata, effective_filters = _build_filters_and_metadata(
user_id=filters.get("user_id"), user_id=user_id, agent_id=agent_id, run_id=run_id, input_metadata=metadata
agent_id=filters.get("agent_id"),
run_id=filters.get("run_id"),
input_metadata=metadata,
) )
if memory_type is not None and memory_type != MemoryType.PROCEDURAL.value: if memory_type is not None and memory_type != MemoryType.PROCEDURAL.value:
@@ -1756,7 +1737,7 @@ class AsyncMemory(MemoryBase):
suggestion="Convert your input to a string, dictionary, or list of dictionaries." suggestion="Convert your input to a string, dictionary, or list of dictionaries."
) )
if filters.get("agent_id") is not None and memory_type == MemoryType.PROCEDURAL.value: if agent_id is not None and memory_type == MemoryType.PROCEDURAL.value:
results = await self._create_procedural_memory( results = await self._create_procedural_memory(
messages, metadata=processed_metadata, prompt=prompt, llm=llm messages, metadata=processed_metadata, prompt=prompt, llm=llm
) )
@@ -2606,23 +2587,26 @@ class AsyncMemory(MemoryBase):
await self._delete_memory(memory_id, existing_memory) await self._delete_memory(memory_id, existing_memory)
return {"message": "Memory deleted successfully!"} return {"message": "Memory deleted successfully!"}
async def delete_all(self, *, filters: Optional[Dict[str, Any]] = None, **kwargs): async def delete_all(self, user_id=None, agent_id=None, run_id=None):
""" """
Delete all memories asynchronously. Delete all memories asynchronously.
Args: Args:
filters (dict, optional): Dictionary containing entity identifiers. Must contain at least one of: user_id (str, optional): ID of the user to delete memories for. Defaults to None.
- user_id: ID of the user to delete memories for agent_id (str, optional): ID of the agent to delete memories for. Defaults to None.
- agent_id: ID of the agent to delete memories for run_id (str, optional): ID of the run to delete memories for. Defaults to None.
- run_id: ID of the run to delete memories for
Example: `{"user_id": "user123"}`
""" """
filters = filters or {} filters = {}
if user_id:
filters["user_id"] = user_id
if agent_id:
filters["agent_id"] = agent_id
if run_id:
filters["run_id"] = run_id
if not any(key in filters for key in ("user_id", "agent_id", "run_id")): if not filters:
raise ValueError( raise ValueError(
"filters must contain at least one of: user_id, agent_id, run_id. " "At least one filter is required to delete all memories. If you want to delete all memories, use the `reset()` method."
"If you want to delete all memories, use the `reset()` method."
) )
keys, encoded_ids = process_telemetry_filters(filters) keys, encoded_ids = process_telemetry_filters(filters)
+2 -5
View File
@@ -57,10 +57,7 @@ def test_add(memory_instance, version):
memory_instance.config.version = version memory_instance.config.version = version
memory_instance._add_to_vector_store = Mock(return_value=[{"memory": "Test memory", "event": "ADD"}]) memory_instance._add_to_vector_store = Mock(return_value=[{"memory": "Test memory", "event": "ADD"}])
result = memory_instance.add( result = memory_instance.add(messages=[{"role": "user", "content": "Test message"}], user_id="test_user")
messages=[{"role": "user", "content": "Test message"}],
filters={"user_id": "test_user"},
)
assert "results" in result assert "results" in result
assert result["results"] == [{"memory": "Test memory", "event": "ADD"}] assert result["results"] == [{"memory": "Test memory", "event": "ADD"}]
@@ -188,7 +185,7 @@ def test_delete_all(memory_instance, version):
memory_instance.vector_store.reset = Mock() memory_instance.vector_store.reset = Mock()
memory_instance._delete_memory = Mock() memory_instance._delete_memory = Mock()
result = memory_instance.delete_all(filters={"user_id": "test_user"}) result = memory_instance.delete_all(user_id="test_user")
assert memory_instance._delete_memory.call_count == 2 assert memory_instance._delete_memory.call_count == 2
# Ensure the collection is NOT dropped — only matched memories should be removed # Ensure the collection is NOT dropped — only matched memories should be removed
+10 -10
View File
@@ -33,20 +33,20 @@ def memory_client():
def test_create_memory(memory_client): def test_create_memory(memory_client):
data = "Name is John Doe." data = "Name is John Doe."
result = memory_client.add([{"role": "user", "content": data}], filters={"user_id": "test_user"}) result = memory_client.add([{"role": "user", "content": data}], user_id="test_user")
assert result["results"][0]["memory"] == data assert result["results"][0]["memory"] == data
def test_get_memory(memory_client): def test_get_memory(memory_client):
data = "Name is John Doe." data = "Name is John Doe."
memory_client.add([{"role": "user", "content": data}], filters={"user_id": "test_user"}) memory_client.add([{"role": "user", "content": data}], user_id="test_user")
result = memory_client.get("1") result = memory_client.get("1")
assert result["memory"] == data assert result["memory"] == data
def test_update_memory(memory_client): def test_update_memory(memory_client):
data = "Name is John Doe." data = "Name is John Doe."
memory_client.add([{"role": "user", "content": data}], filters={"user_id": "test_user"}) memory_client.add([{"role": "user", "content": data}], user_id="test_user")
new_data = "Name is John Kapoor." new_data = "Name is John Kapoor."
update_result = memory_client.update("1", text=new_data) update_result = memory_client.update("1", text=new_data)
assert update_result["message"] == "Memory updated successfully!" assert update_result["message"] == "Memory updated successfully!"
@@ -54,14 +54,14 @@ def test_update_memory(memory_client):
def test_delete_memory(memory_client): def test_delete_memory(memory_client):
data = "Name is John Doe." data = "Name is John Doe."
memory_client.add([{"role": "user", "content": data}], filters={"user_id": "test_user"}) memory_client.add([{"role": "user", "content": data}], user_id="test_user")
delete_result = memory_client.delete("1") delete_result = memory_client.delete("1")
assert delete_result["message"] == "Memory deleted successfully!" assert delete_result["message"] == "Memory deleted successfully!"
def test_history(memory_client): def test_history(memory_client):
data = "I like Indian food." data = "I like Indian food."
memory_client.add([{"role": "user", "content": data}], filters={"user_id": "test_user"}) memory_client.add([{"role": "user", "content": data}], user_id="test_user")
memory_client.update("1", text="I like Italian food.") memory_client.update("1", text="I like Italian food.")
history = memory_client.history("1") history = memory_client.history("1")
assert history[0]["memory"] == "I like Indian food." assert history[0]["memory"] == "I like Indian food."
@@ -71,8 +71,8 @@ def test_history(memory_client):
def test_list_memories(memory_client): def test_list_memories(memory_client):
data1 = "Name is John Doe." data1 = "Name is John Doe."
data2 = "Name is John Doe. I like to code in Python." data2 = "Name is John Doe. I like to code in Python."
memory_client.add([{"role": "user", "content": data1}], filters={"user_id": "test_user"}) memory_client.add([{"role": "user", "content": data1}], user_id="test_user")
memory_client.add([{"role": "user", "content": data2}], filters={"user_id": "test_user"}) memory_client.add([{"role": "user", "content": data2}], user_id="test_user")
memories = memory_client.get_all(filters={"user_id": "test_user"}) memories = memory_client.get_all(filters={"user_id": "test_user"})
assert data1 in memories assert data1 in memories
assert data2 in memories assert data2 in memories
@@ -469,7 +469,7 @@ def test_add_infer_false_embeds_once(mock_sqlite, mock_llm_factory, mock_vector_
from mem0.memory.main import Memory as MemoryClass from mem0.memory.main import Memory as MemoryClass
memory = MemoryClass(MemoryConfig()) memory = MemoryClass(MemoryConfig())
memory.add("foo", filters={"user_id": "test_user"}, infer=False) memory.add("foo", user_id="test_user", infer=False)
assert embedder.embed.call_count == 1 assert embedder.embed.call_count == 1
mock_vector_store.insert.assert_called_once() mock_vector_store.insert.assert_called_once()
@@ -511,7 +511,7 @@ def test_add_infer_true_caches_embedding_on_llm_rewrite(mock_sqlite, mock_llm_fa
from mem0.memory.main import Memory as MemoryClass from mem0.memory.main import Memory as MemoryClass
memory = MemoryClass(MemoryConfig()) memory = MemoryClass(MemoryConfig())
memory.add("I like Python", filters={"user_id": "test_user"}, infer=True) memory.add("I like Python", user_id="test_user", infer=True)
# V3 pipeline: embed called once for search query (Phase 1), # V3 pipeline: embed called once for search query (Phase 1),
# embed_batch called once for extracted memories (Phase 3) # embed_batch called once for extracted memories (Phase 3)
@@ -568,7 +568,7 @@ def test_update_infer_true_caches_embedding_on_llm_rewrite(mock_sqlite, mock_llm
from mem0.memory.main import Memory as MemoryClass from mem0.memory.main import Memory as MemoryClass
memory = MemoryClass(MemoryConfig()) memory = MemoryClass(MemoryConfig())
memory.add("I love Python now", filters={"user_id": "test_user"}, infer=True) memory.add("I love Python now", user_id="test_user", infer=True)
# V3 pipeline: embed called once for search query (Phase 1), # V3 pipeline: embed called once for search query (Phase 1),
# embed_batch called once for extracted memories (Phase 3) # embed_batch called once for extracted memories (Phase 3)