fix(sdk): removing deprecating param from our sdk and docs changes with it (#4740)

This commit is contained in:
Kartik
2026-04-12 00:34:58 +05:30
committed by GitHub
parent 3882af7450
commit e44b46ef2e
138 changed files with 1186 additions and 2840 deletions
+7 -2
View File
@@ -3,14 +3,17 @@ import type * as MemoryTypes from "./mem0.types";
// Re-export all types from mem0.types
export type {
MemoryOptions,
EntityOptions,
AddMemoryOptions,
SearchMemoryOptions,
GetAllMemoryOptions,
DeleteAllMemoryOptions,
ProjectOptions,
Memory,
MemoryHistory,
MemoryUpdateBody,
ProjectResponse,
PromptUpdatePayload,
SearchOptions,
Webhook,
WebhookCreatePayload,
WebhookUpdatePayload,
@@ -19,6 +22,8 @@ export type {
AllUsers,
User,
FeedbackPayload,
CreateMemoryExportPayload,
GetMemoryExportPayload,
} from "./mem0.types";
// Re-export enums as values (not type-only)
+57 -205
View File
@@ -4,11 +4,13 @@ import {
ProjectOptions,
Memory,
MemoryHistory,
MemoryOptions,
AddMemoryOptions,
SearchMemoryOptions,
GetAllMemoryOptions,
DeleteAllMemoryOptions,
MemoryUpdateBody,
ProjectResponse,
PromptUpdatePayload,
SearchOptions,
Webhook,
WebhookCreatePayload,
WebhookUpdatePayload,
@@ -30,19 +32,13 @@ class APIError extends Error {
interface ClientOptions {
apiKey: string;
host?: string;
organizationName?: string;
projectName?: string;
organizationId?: string;
projectId?: string;
}
export default class MemoryClient {
apiKey: string;
host: string;
organizationName: string | null;
projectName: string | null;
organizationId: string | number | null;
projectId: string | number | null;
private organizationId: string | number | null;
private projectId: string | number | null;
headers: Record<string, string>;
client: any;
telemetryId: string;
@@ -59,35 +55,11 @@ export default class MemoryClient {
}
}
_validateOrgProject(): void {
// Check for organizationName/projectName pair
if (
(this.organizationName === null && this.projectName !== null) ||
(this.organizationName !== null && this.projectName === null)
) {
console.warn(
"Warning: Both organizationName and projectName must be provided together when using either. This will be removed from version 1.0.40. Note that organizationName/projectName are being deprecated in favor of organizationId/projectId.",
);
}
// Check for organizationId/projectId pair
if (
(this.organizationId === null && this.projectId !== null) ||
(this.organizationId !== null && this.projectId === null)
) {
console.warn(
"Warning: Both organizationId and projectId must be provided together when using either. This will be removed from version 1.0.40.",
);
}
}
constructor(options: ClientOptions) {
this.apiKey = options.apiKey;
this.host = options.host || "https://api.mem0.ai";
this.organizationName = options.organizationName || null;
this.projectName = options.projectName || null;
this.organizationId = options.organizationId || null;
this.projectId = options.projectId || null;
this.organizationId = null;
this.projectId = null;
this.headers = {
Authorization: `Token ${this.apiKey}`,
@@ -101,28 +73,19 @@ export default class MemoryClient {
});
this._validateApiKey();
// Initialize with a temporary ID that will be updated
this.telemetryId = "";
// Initialize the client
this._initializeClient();
}
private async _initializeClient() {
try {
// Generate telemetry ID
await this.ping();
if (!this.telemetryId) {
this.telemetryId = generateHash(this.apiKey);
}
this._validateOrgProject();
// Capture initialization event
captureClientEvent("init", this, {
api_version: "v1",
client_type: "MemoryClient",
}).catch((error: any) => {
console.error("Failed to capture event:", error);
@@ -163,13 +126,16 @@ export default class MemoryClient {
return jsonResponse;
}
_preparePayload(messages: Array<Message>, options: MemoryOptions): object {
_preparePayload(
messages: Array<Message>,
options: Record<string, any>,
): object {
const payload: any = {};
payload.messages = messages;
return { ...payload, ...options };
}
_prepareParams(options: MemoryOptions): object {
_prepareParams(options: Record<string, any>): object {
return Object.fromEntries(
Object.entries(options).filter(([_, v]) => v != null),
);
@@ -197,9 +163,8 @@ export default class MemoryClient {
const { org_id, project_id, user_email } = response;
// Only update if values are actually present
if (org_id && !this.organizationId) this.organizationId = org_id;
if (project_id && !this.projectId) this.projectId = project_id;
if (org_id) this.organizationId = org_id;
if (project_id) this.projectId = project_id;
if (user_email) this.telemetryId = user_email;
} catch (error: any) {
// Pass through structured exceptions and APIError
@@ -215,30 +180,11 @@ export default class MemoryClient {
async add(
messages: Array<Message>,
options: MemoryOptions & Record<string, any> = {},
options: AddMemoryOptions & Record<string, any> = {},
): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
if (this.organizationName != null && this.projectName != null) {
options.org_name = this.organizationName;
options.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
options.org_id = this.organizationId;
options.project_id = this.projectId;
if (options.org_name) delete options.org_name;
if (options.project_name) delete options.project_name;
}
if (options.api_version) {
options.version = options.api_version.toString() || "v2";
}
const payload = this._preparePayload(messages, options);
// get payload keys whose value is not null or undefined
const payloadKeys = Object.keys(payload);
this._captureEvent("add", [payloadKeys]);
@@ -276,7 +222,6 @@ export default class MemoryClient {
}
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payload: Record<string, any> = {};
if (text !== undefined) payload.text = text;
if (metadata !== undefined) payload.metadata = metadata;
@@ -307,87 +252,53 @@ export default class MemoryClient {
);
}
async getAll(options?: SearchOptions): Promise<Array<Memory>> {
async getAll(options?: GetAllMemoryOptions): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("get_all", [payloadKeys]);
const { api_version, page, page_size, ...otherOptions } = options ?? {};
if (this.organizationName != null && this.projectName != null) {
otherOptions.org_name = this.organizationName;
otherOptions.project_name = this.projectName;
}
let appendedParams = "";
let paginated_response = false;
const { page, page_size, ...rest } = options ?? {};
const body: Record<string, any> = {
output_format: "v1.1",
...rest,
};
let url = `${this.host}/v2/memories/`;
if (page && page_size) {
appendedParams += `page=${page}&page_size=${page_size}`;
paginated_response = true;
url += `?page=${page}&page_size=${page_size}`;
}
if (this.organizationId != null && this.projectId != null) {
otherOptions.org_id = this.organizationId;
otherOptions.project_id = this.projectId;
if (otherOptions.org_name) delete otherOptions.org_name;
if (otherOptions.project_name) delete otherOptions.project_name;
}
if (api_version === "v2") {
let url = paginated_response
? `${this.host}/v2/memories/?${appendedParams}`
: `${this.host}/v2/memories/`;
return this._fetchWithErrorHandling(url, {
method: "POST",
headers: this.headers,
body: JSON.stringify(otherOptions),
});
} else {
// @ts-ignore
const params = new URLSearchParams(this._prepareParams(otherOptions));
const url = paginated_response
? `${this.host}/v1/memories/?${params}&${appendedParams}`
: `${this.host}/v1/memories/?${params}`;
return this._fetchWithErrorHandling(url, {
headers: this.headers,
});
}
const response = await this._fetchWithErrorHandling(url, {
method: "POST",
headers: this.headers,
body: JSON.stringify(body),
});
// Unwrap v1.1 format: { results: [...] } → [...]
return Array.isArray(response) ? response : (response?.results ?? response);
}
async search(
query: string,
options?: SearchOptions & Record<string, any>,
options?: SearchMemoryOptions,
): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("search", [payloadKeys]);
const { api_version, ...otherOptions } = options ?? {};
const payload = { query, ...otherOptions };
if (this.organizationName != null && this.projectName != null) {
payload.org_name = this.organizationName;
payload.project_name = this.projectName;
}
const payload: Record<string, any> = {
query,
output_format: "v1.1",
...(options ?? {}),
};
if (this.organizationId != null && this.projectId != null) {
payload.org_id = this.organizationId;
payload.project_id = this.projectId;
if (payload.org_name) delete payload.org_name;
if (payload.project_name) delete payload.project_name;
}
const endpoint =
api_version === "v2" ? "/v2/memories/search/" : "/v1/memories/search/";
const response = await this._fetchWithErrorHandling(
`${this.host}${endpoint}`,
`${this.host}/v2/memories/search/`,
{
method: "POST",
headers: this.headers,
body: JSON.stringify(payload),
},
);
return response;
// Unwrap v1.1 format: { results: [...] } → [...]
return Array.isArray(response) ? response : (response?.results ?? response);
}
async delete(memoryId: string): Promise<{ message: string }> {
@@ -402,23 +313,12 @@ export default class MemoryClient {
);
}
async deleteAll(options: MemoryOptions = {}): Promise<{ message: string }> {
async deleteAll(
options: DeleteAllMemoryOptions = {},
): Promise<{ message: string }> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("delete_all", [payloadKeys]);
if (this.organizationName != null && this.projectName != null) {
options.org_name = this.organizationName;
options.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
options.org_id = this.organizationId;
options.project_id = this.projectId;
if (options.org_name) delete options.org_name;
if (options.project_name) delete options.project_name;
}
// @ts-ignore
const params = new URLSearchParams(this._prepareParams(options));
const response = await this._fetchWithErrorHandling(
@@ -443,31 +343,20 @@ export default class MemoryClient {
return response;
}
async users(): Promise<AllUsers> {
async users(options?: {
page?: number;
page_size?: number;
}): Promise<AllUsers> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
this._captureEvent("users", []);
const options: MemoryOptions = {};
if (this.organizationName != null && this.projectName != null) {
options.org_name = this.organizationName;
options.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
options.org_id = this.organizationId;
options.project_id = this.projectId;
if (options.org_name) delete options.org_name;
if (options.project_name) delete options.project_name;
}
// @ts-ignore
const params = new URLSearchParams(options);
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/entities/?${params}`,
{
headers: this.headers,
},
);
let url = `${this.host}/v1/entities/`;
const params: string[] = [];
if (options?.page) params.push(`page=${options.page}`);
if (options?.page_size) params.push(`page_size=${options.page_size}`);
if (params.length) url += `?${params.join("&")}`;
const response = await this._fetchWithErrorHandling(url, {
headers: this.headers,
});
return response;
}
@@ -502,7 +391,6 @@ export default class MemoryClient {
} = {},
): Promise<{ message: string }> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
let to_delete: Array<{ type: string; name: string }> = [];
const { user_id, agent_id, app_id, run_id } = params;
@@ -527,29 +415,9 @@ export default class MemoryClient {
throw new Error("No entities to delete");
}
const requestOptions: MemoryOptions = {};
if (this.organizationName != null && this.projectName != null) {
requestOptions.org_name = this.organizationName;
requestOptions.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
requestOptions.org_id = this.organizationId;
requestOptions.project_id = this.projectId;
if (requestOptions.org_name) delete requestOptions.org_name;
if (requestOptions.project_name) delete requestOptions.project_name;
}
// Delete each entity and handle errors
for (const entity of to_delete) {
try {
await this.client.delete(
`/v2/entities/${entity.type}/${entity.name}/`,
{
params: requestOptions,
},
);
await this.client.delete(`/v2/entities/${entity.type}/${entity.name}/`);
} catch (error: any) {
throw new APIError(
`Failed to delete ${entity.type} ${entity.name}: ${error.message}`,
@@ -558,13 +426,7 @@ export default class MemoryClient {
}
this._captureEvent("delete_users", [
{
user_id: user_id,
agent_id: agent_id,
app_id: app_id,
run_id: run_id,
sync_type: "sync",
},
{ user_id, agent_id, app_id, run_id, sync_type: "sync" },
]);
return {
@@ -612,7 +474,6 @@ export default class MemoryClient {
async getProject(options: ProjectOptions): Promise<ProjectResponse> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("get_project", [payloadKeys]);
const { fields } = options;
@@ -639,7 +500,6 @@ export default class MemoryClient {
prompts: PromptUpdatePayload,
): Promise<Record<string, any>> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
this._captureEvent("update_project", []);
if (!(this.organizationId && this.projectId)) {
throw new Error(
@@ -748,15 +608,10 @@ export default class MemoryClient {
if (this.telemetryId === "") await this.ping();
this._captureEvent("create_memory_export", []);
// Return if missing filters or schema
if (!data.filters || !data.schema) {
throw new Error("Missing filters or schema");
}
// Add Org and Project ID
data.org_id = this.organizationId?.toString() || null;
data.project_id = this.projectId?.toString() || null;
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/exports/`,
{
@@ -779,9 +634,6 @@ export default class MemoryClient {
throw new Error("Missing memory_export_id or filters");
}
data.org_id = this.organizationId?.toString() || "";
data.project_id = this.projectId?.toString() || "";
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/exports/get/`,
{
+63 -76
View File
@@ -1,59 +1,70 @@
interface Common {
project_id?: string | null;
org_id?: string | null;
}
export interface MemoryOptions {
api_version?: API_VERSION | string;
version?: API_VERSION | string;
// ─── Entity Options (for add/delete — top-level identity) ───
export interface EntityOptions {
user_id?: string;
agent_id?: string;
app_id?: string;
run_id?: string;
}
// ─── Per-Method Options ─────────────────────────────────────
export interface AddMemoryOptions extends EntityOptions {
metadata?: Record<string, any>;
filters?: Record<string, any>;
org_name?: string | null; // Deprecated
project_name?: string | null; // Deprecated
org_id?: string | number | null;
project_id?: string | number | null;
infer?: boolean;
page?: number;
page_size?: number;
includes?: string;
excludes?: string;
enable_graph?: boolean;
start_date?: string;
end_date?: string;
custom_categories?: custom_categories[];
custom_instructions?: string;
timestamp?: number;
output_format?: string | OutputFormat;
async_mode?: boolean;
filter_memories?: boolean;
immutable?: boolean;
structured_data_schema?: Record<string, any>;
enable_graph?: boolean;
}
export interface SearchMemoryOptions {
filters?: Record<string, any>;
metadata?: Record<string, any>;
top_k?: number;
threshold?: number;
rerank?: boolean;
fields?: string[];
categories?: string[];
enable_graph?: boolean;
}
export interface GetAllMemoryOptions {
filters?: Record<string, any>;
page?: number;
page_size?: number;
start_date?: string;
end_date?: string;
categories?: string[];
enable_graph?: boolean;
}
export interface DeleteAllMemoryOptions extends EntityOptions {}
// ─── Project Options ────────────────────────────────────────
export interface ProjectOptions {
fields?: string[];
}
export enum OutputFormat {
V1 = "v1.0",
V1_1 = "v1.1",
}
export enum API_VERSION {
V1 = "v1",
V2 = "v2",
export interface PromptUpdatePayload {
custom_instructions?: string;
custom_categories?: custom_categories[];
retrieval_criteria?: any[];
enable_graph?: boolean;
version?: string;
memory_depth?: string | null;
usecase_setting?: string | number;
multilingual?: boolean;
[key: string]: any;
}
// ─── Enums ──────────────────────────────────────────────────
export enum Feedback {
POSITIVE = "POSITIVE",
NEGATIVE = "NEGATIVE",
VERY_NEGATIVE = "VERY_NEGATIVE",
}
// ─── Message Types ──────────────────────────────────────────
export interface MultiModalMessages {
type: "image_url";
image_url: {
@@ -68,30 +79,9 @@ export interface Messages {
export interface Message extends Messages {}
export interface MemoryHistory {
id: string;
memory_id: string;
input: Array<Messages>;
old_memory: string | null;
new_memory: string | null;
user_id: string;
categories: Array<string>;
event: Event | string;
created_at: Date;
updated_at: Date;
}
export interface SearchOptions extends MemoryOptions {
api_version?: API_VERSION | string;
limit?: number;
enable_graph?: boolean;
threshold?: number;
top_k?: number;
only_metadata_based_search?: boolean;
keyword_search?: boolean;
fields?: string[];
categories?: string[];
rerank?: boolean;
// ─── Response Types (reflect API shapes, unchanged) ─────────
export interface MemoryData {
memory: string;
}
enum Event {
@@ -101,10 +91,6 @@ enum Event {
NOOP = "NOOP",
}
export interface MemoryData {
memory: string;
}
export interface Memory {
id: string;
messages?: Array<Messages>;
@@ -125,6 +111,19 @@ export interface Memory {
run_id?: string | null;
}
export interface MemoryHistory {
id: string;
memory_id: string;
input: Array<Messages>;
old_memory: string | null;
new_memory: string | null;
user_id: string;
categories: Array<string>;
event: Event | string;
created_at: Date;
updated_at: Date;
}
export interface MemoryUpdateBody {
memoryId: string;
text: string;
@@ -157,20 +156,7 @@ interface custom_categories {
[key: string]: any;
}
export interface PromptUpdatePayload {
custom_instructions?: string;
custom_categories?: custom_categories[];
retrieval_criteria?: any[];
enable_graph?: boolean;
version?: string;
inclusion_prompt?: string;
exclusion_prompt?: string;
memory_depth?: string | null;
usecase_setting?: string | number;
multilingual?: boolean;
[key: string]: any;
}
// ─── Webhook Types ──────────────────────────────────────────
export enum WebhookEvent {
MEMORY_ADDED = "memory_add",
MEMORY_UPDATED = "memory_update",
@@ -202,19 +188,20 @@ export interface WebhookUpdatePayload {
eventTypes?: WebhookEvent[];
}
// ─── Feedback & Export Types ────────────────────────────────
export interface FeedbackPayload {
memory_id: string;
feedback?: Feedback | null;
feedback_reason?: string | null;
}
export interface CreateMemoryExportPayload extends Common {
export interface CreateMemoryExportPayload {
schema: Record<string, any>;
filters: Record<string, any>;
export_instructions?: string;
}
export interface GetMemoryExportPayload extends Common {
export interface GetMemoryExportPayload {
filters?: Record<string, any>;
memory_export_id?: string;
}
@@ -122,7 +122,9 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
// ─── Get all ──────────────────────────────────────────────
describe("get all memories", () => {
test("returns all memories for test user", async () => {
const memories = await client.getAll({ user_id: TEST_USER_ID });
const memories = await client.getAll({
filters: { user_id: TEST_USER_ID },
});
expect(Array.isArray(memories)).toBe(true);
expect(memories.length).toBeGreaterThanOrEqual(memoryIds.length);
@@ -135,7 +137,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
test("returns paginated results with page and page_size", async () => {
const page1 = await client.getAll({
user_id: TEST_USER_ID,
filters: { user_id: TEST_USER_ID },
page: 1,
page_size: 1,
});
@@ -200,7 +202,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
test("getAll for non-existent user returns empty array", async () => {
const memories = await client.getAll({
user_id: `nonexistent-user-${randomUUID()}`,
filters: { user_id: `nonexistent-user-${randomUUID()}` },
});
expect(Array.isArray(memories)).toBe(true);
@@ -63,7 +63,9 @@ export async function waitForMemories(
maxRetries = 4,
): Promise<Memory[]> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const memories = await withRetry(() => client.getAll({ user_id: userId }));
const memories = await withRetry(() =>
client.getAll({ filters: { user_id: userId } }),
);
if (Array.isArray(memories) && memories.length >= minCount) {
return memories;
}
@@ -31,10 +31,10 @@ describeIntegration("MemoryClient Integration — Initialization", () => {
afterAll(() => cleanup());
test("client pings successfully and resolves org/project", async () => {
test("client pings successfully", async () => {
await client.ping();
expect(client.organizationId).toBeTruthy();
expect(client.projectId).toBeTruthy();
// org/project are now resolved internally from the API key
expect(client.telemetryId).toBeTruthy();
});
test("get with invalid ID throws ValidationError", async () => {
@@ -1,7 +1,7 @@
/**
* Integration tests: Search and history operations.
*
* Tests search v1, search v2, and memory history against the real API.
* Tests search, filtered search, and memory history against the real API.
*
* Run: MEM0_API_KEY=your-key npx jest search.test.ts --forceExit
*/
@@ -36,14 +36,14 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
cleanup();
});
// ─── Search v1 ────────────────────────────────────────────
describe("search v1", () => {
// ─── Search ─────────────────────────────────────────────
describe("search", () => {
test("searches memories by user_id and returns results with scores", async () => {
// Search index may lag behind listing index — poll until ready
const results = await waitForSearchResults(
client,
"What is my favorite color?",
{ user_id: TEST_USER_ID },
{ filters: { user_id: TEST_USER_ID } },
);
expect(Array.isArray(results)).toBe(true);
@@ -57,15 +57,14 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
});
});
// ─── Search v2 ────────────────────────────────────────────
describe("search v2", () => {
// ─── Search with filters ─────────────────────────────────
describe("search with filters", () => {
test("searches with OR filters and returns results", async () => {
const results = await waitForSearchResults(
client,
"What do you know about me?",
{
filters: { OR: [{ user_id: TEST_USER_ID }] },
api_version: "v2",
},
);
@@ -110,19 +109,19 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
describe("edge cases", () => {
test("search for non-existent user returns empty results", async () => {
const results = await client.search("anything", {
user_id: `nonexistent-user-${randomUUID()}`,
filters: { user_id: `nonexistent-user-${randomUUID()}` },
});
expect(Array.isArray(results)).toBe(true);
expect(results.length).toBe(0);
});
test("search with limit param does not throw", async () => {
test("search with top_k param does not throw", async () => {
const results = await client.search(
"Tell me about integration test user",
{
user_id: TEST_USER_ID,
limit: 1,
filters: { user_id: TEST_USER_ID },
top_k: 1,
},
);
@@ -1,15 +1,13 @@
/**
* MemoryClient unit tests — add, get, getAll, update, delete, deleteAll, history.
* MemoryClient unit tests — add, get, update, delete, deleteAll, history.
* Tests verify request construction, not mock response echo.
*/
import { MemoryClient } from "../mem0";
import type { Memory, MemoryHistory } from "../mem0.types";
import type { MemoryHistory } from "../mem0.types";
import {
createMockMemory,
createMockMemoryHistory,
TEST_API_KEY,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import {
setupMockFetch,
@@ -61,40 +59,6 @@ describe("MemoryClient - add()", () => {
expect(getFetchBody(call!).user_id).toBe("user_1");
});
test("attaches org_id from constructor to payload", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.add([{ role: "user", content: "test" }], { user_id: "u1" });
const call = findFetchCall(mock, "/v1/memories/", "POST");
const body = getFetchBody(call!);
expect(body.org_id).toBe(TEST_ORG_ID);
});
test("attaches project_id from constructor to payload", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.add([{ role: "user", content: "test" }], { user_id: "u1" });
const call = findFetchCall(mock, "/v1/memories/", "POST");
const body = getFetchBody(call!);
expect(body.project_id).toBe(TEST_PROJECT_ID);
});
test("sends empty messages array without crashing", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [] });
@@ -142,67 +106,6 @@ describe("MemoryClient - get()", () => {
});
});
// ─── getAll() ────────────────────────────────────────────
describe("MemoryClient - getAll()", () => {
test("uses v2 POST endpoint when api_version=v2", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.getAll({ user_id: "u1", api_version: "v2" });
expect(findFetchCall(mock, "/v2/memories/", "POST")).toBeDefined();
});
test("uses v1 GET endpoint by default with user_id as query param", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.getAll({ user_id: "u1" });
const call = mock.mock.calls.find(
(c: [string, RequestInit]) =>
c[0].includes("/v1/memories/?") && !c[1]?.method,
);
expect(call).toBeDefined();
expect(call![0]).toContain("user_id=u1");
});
test("appends page and page_size to URL as query params", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.getAll({
user_id: "u1",
api_version: "v2",
page: 2,
page_size: 25,
});
const call = mock.mock.calls.find((c: [string, RequestInit]) =>
c[0].includes("page="),
);
expect(call![0]).toContain("page=2");
expect(call![0]).toContain("page_size=25");
});
test("does not crash when called without options", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [] });
setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
const result: Memory[] = await client.getAll();
expect(Array.isArray(result)).toBe(true);
});
});
// ─── update() ────────────────────────────────────────────
describe("MemoryClient - update()", () => {
@@ -7,13 +7,7 @@ import {
ValidationError,
MemoryError,
} from "../../common/exceptions";
import {
createMockFetch,
TEST_API_KEY,
TEST_HOST,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import { createMockFetch, TEST_API_KEY, TEST_HOST } from "./helpers";
import {
setupMockFetch,
installConsoleSuppression,
@@ -55,24 +49,6 @@ describe("MemoryClient - Initialization", () => {
expect(client.host).toBe(TEST_HOST);
});
test("sets organizationId from constructor", () => {
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
expect(client.organizationId).toBe(TEST_ORG_ID);
});
test("sets projectId from constructor", () => {
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
expect(client.projectId).toBe(TEST_PROJECT_ID);
});
test("sets Authorization header with Token prefix", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
expect(client.headers["Authorization"]).toBe(`Token ${TEST_API_KEY}`);
@@ -87,20 +63,6 @@ describe("MemoryClient - Initialization", () => {
// ─── Ping ────────────────────────────────────────────────
describe("MemoryClient - ping()", () => {
test("sets organizationId from ping response", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
expect(client.organizationId).toBe(TEST_ORG_ID);
});
test("sets projectId from ping response", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
expect(client.projectId).toBe(TEST_PROJECT_ID);
});
test("sets telemetryId from user_email in response", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
@@ -108,28 +70,6 @@ describe("MemoryClient - ping()", () => {
expect(client.telemetryId).toBe("test@example.com");
});
test("preserves constructor organizationId over ping response", async () => {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: "my_org",
projectId: "my_proj",
});
await client.ping();
expect(client.organizationId).toBe("my_org");
});
test("preserves constructor projectId over ping response", async () => {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: "my_org",
projectId: "my_proj",
});
await client.ping();
expect(client.projectId).toBe("my_proj");
});
test("throws AuthenticationError on 401 response", async () => {
const { AuthenticationError } = await import("../../common/exceptions");
const responses = new Map<string, { status: number; body: unknown }>();
@@ -4,12 +4,7 @@
*/
import { MemoryClient } from "../mem0";
import { Feedback } from "../mem0.types";
import {
createMockFetch,
TEST_API_KEY,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import { createMockFetch, TEST_API_KEY } from "./helpers";
import {
setupMockFetch,
findFetchCall,
@@ -22,7 +17,7 @@ installConsoleSuppression();
// ─── getProject() ───────────────────────────────────────
describe("MemoryClient - getProject()", () => {
test("throws when organizationId and projectId not set", async () => {
test("throws when organizationId and projectId not set (ping returns no org)", async () => {
const responses = new Map<string, { status: number; body: unknown }>();
responses.set("/v1/ping/", { status: 200, body: { status: "ok" } });
global.fetch = createMockFetch(responses);
@@ -47,11 +42,9 @@ describe("MemoryClient - getProject()", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
// org/project come from ping mock response
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
await client.getProject({ fields: ["custom_instructions"] });
const call = mock.mock.calls.find(
@@ -74,11 +67,8 @@ describe("MemoryClient - updateProject()", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
await client.updateProject({
custom_instructions: "Updated instructions",
});
@@ -95,11 +85,8 @@ describe("MemoryClient - updateProject()", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
await client.updateProject({
custom_instructions: "Updated instructions",
});
@@ -161,11 +148,7 @@ describe("MemoryClient - feedback()", () => {
describe("MemoryClient - Memory Exports", () => {
test("createMemoryExport throws when missing filters or schema", async () => {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(
client.createMemoryExport({
filters: null as never,
@@ -182,11 +165,7 @@ describe("MemoryClient - Memory Exports", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.createMemoryExport({
schema: { fields: ["memory", "user_id"] },
filters: { user_id: "u1" },
@@ -195,37 +174,9 @@ describe("MemoryClient - Memory Exports", () => {
expect(findFetchCall(mock, "/v1/exports/", "POST")).toBeDefined();
});
test("createMemoryExport attaches org_id and project_id to body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/exports/", {
status: 200,
body: { message: "Created", id: "exp_1" },
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.createMemoryExport({
schema: { fields: ["memory"] },
filters: { user_id: "u1" },
});
const call = findFetchCall(mock, "/v1/exports/", "POST");
const body = getFetchBody(call!);
expect(body.org_id).toBe(TEST_ORG_ID);
expect(body.project_id).toBe(TEST_PROJECT_ID);
});
test("getMemoryExport throws when missing both id and filters", async () => {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(client.getMemoryExport({} as never)).rejects.toThrow(
"Missing memory_export_id or filters",
);
@@ -239,11 +190,7 @@ describe("MemoryClient - Memory Exports", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.getMemoryExport({ memory_export_id: "exp_123" });
expect(findFetchCall(mock, "/v1/exports/get/", "POST")).toBeDefined();
@@ -1,5 +1,5 @@
/**
* MemoryClient unit tests — search (v1/v2 routing, filters).
* MemoryClient unit tests — search (v2 default, filters).
* Tests verify request construction, not mock response echo.
*/
import { MemoryClient } from "../mem0";
@@ -15,60 +15,52 @@ import {
installConsoleSuppression();
describe("MemoryClient - search()", () => {
test("sends POST to /v1/memories/search/ by default", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("What is my name?", { user_id: "u1" });
expect(findFetchCall(mock, "/v1/memories/search/", "POST")).toBeDefined();
});
test("includes query in request body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("What is my name?", { user_id: "u1" });
const call = findFetchCall(mock, "/v1/memories/search/", "POST");
expect(getFetchBody(call!).query).toBe("What is my name?");
});
test("includes user_id in request body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("test", { user_id: "u1" });
const call = findFetchCall(mock, "/v1/memories/search/", "POST");
expect(getFetchBody(call!).user_id).toBe("u1");
});
test("uses /v2/memories/search/ when api_version=v2", async () => {
test("sends POST to /v2/memories/search/ by default", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("test", { user_id: "u1", api_version: "v2" });
await client.search("What is my name?", {
filters: { user_id: "u1" },
});
expect(findFetchCall(mock, "/v2/memories/search/", "POST")).toBeDefined();
});
test("passes filters through to the v2 API body", async () => {
test("includes query in request body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("What is my name?", {
filters: { user_id: "u1" },
});
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
expect(getFetchBody(call!).query).toBe("What is my name?");
});
test("passes filters through to the API body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("test", { filters: { user_id: "u1" } });
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
expect(getFetchBody(call!).filters).toEqual({ user_id: "u1" });
});
test("passes complex OR filters through to the API body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("query", {
api_version: "v2",
filters: { OR: [{ user_id: "u1" }, { agent_id: "a1" }] },
});
@@ -81,7 +73,7 @@ describe("MemoryClient - search()", () => {
test("does not crash when called without options", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/search/", { status: 200, body: [] });
extra.set("/v2/memories/search/", { status: 200, body: [] });
setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
@@ -91,12 +83,12 @@ describe("MemoryClient - search()", () => {
test("handles empty results array", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/search/", { status: 200, body: [] });
extra.set("/v2/memories/search/", { status: 200, body: [] });
setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
const result: Memory[] = await client.search("nonexistent query", {
user_id: "u1",
filters: { user_id: "u1" },
});
expect(result).toHaveLength(0);
});
@@ -1,15 +1,9 @@
/**
* MemoryClient unit tests — users, deleteUser, deleteUsers.
* MemoryClient unit tests — users, deleteUser.
* Tests verify entity type routing and request construction.
*/
import { MemoryClient } from "../mem0";
import {
createMockUser,
createMockAllUsers,
TEST_API_KEY,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import { createMockUser, createMockAllUsers, TEST_API_KEY } from "./helpers";
import {
setupMockFetch,
findFetchCall,
@@ -40,111 +34,6 @@ describe("MemoryClient - users()", () => {
});
});
// ─── deleteUsers() ──────────────────────────────────────
describe("MemoryClient - deleteUsers()", () => {
function createClientWithMockedAxios() {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const axiosDeleteMock = jest
.fn()
.mockResolvedValue({ data: { message: "Deleted" } });
client.client.delete = axiosDeleteMock;
return { client, axiosDeleteMock };
}
test("routes user_id to DELETE /v2/entities/user/:name/", async () => {
const { client, axiosDeleteMock } = createClientWithMockedAxios();
await client.deleteUsers({ user_id: "u1" });
expect(axiosDeleteMock).toHaveBeenCalledWith("/v2/entities/user/u1/", {
params: expect.objectContaining({
org_id: TEST_ORG_ID,
project_id: TEST_PROJECT_ID,
}),
});
});
test("routes agent_id to DELETE /v2/entities/agent/:name/", async () => {
const { client, axiosDeleteMock } = createClientWithMockedAxios();
await client.deleteUsers({ agent_id: "agent_1" });
expect(axiosDeleteMock).toHaveBeenCalledWith(
"/v2/entities/agent/agent_1/",
expect.any(Object),
);
});
test("routes app_id to DELETE /v2/entities/app/:name/", async () => {
const { client, axiosDeleteMock } = createClientWithMockedAxios();
await client.deleteUsers({ app_id: "app_1" });
expect(axiosDeleteMock).toHaveBeenCalledWith(
"/v2/entities/app/app_1/",
expect.any(Object),
);
});
test("routes run_id to DELETE /v2/entities/run/:name/", async () => {
const { client, axiosDeleteMock } = createClientWithMockedAxios();
await client.deleteUsers({ run_id: "run_1" });
expect(axiosDeleteMock).toHaveBeenCalledWith(
"/v2/entities/run/run_1/",
expect.any(Object),
);
});
test("returns 'Entity deleted successfully.' for single entity", async () => {
const { client } = createClientWithMockedAxios();
const result = await client.deleteUsers({ user_id: "u1" });
expect(result.message).toBe("Entity deleted successfully.");
});
test("returns 'All users, agents, apps and runs deleted.' when no params given", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/entities/", {
status: 200,
body: createMockAllUsers([createMockUser({ name: "u1", type: "user" })]),
});
setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
client.client.delete = jest
.fn()
.mockResolvedValue({ data: { message: "Deleted" } });
const result = await client.deleteUsers();
expect(result.message).toBe("All users, agents, apps and runs deleted.");
});
test("throws when no entities exist to delete", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/entities/", {
status: 200,
body: createMockAllUsers([]),
});
setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
client.client.delete = jest.fn();
await expect(client.deleteUsers()).rejects.toThrow("No entities to delete");
});
});
// ─── deleteUser() (deprecated) ──────────────────────────
describe("MemoryClient - deleteUser() (deprecated)", () => {
@@ -5,7 +5,7 @@
*/
import { MemoryClient } from "../mem0";
import { WebhookEvent } from "../mem0.types";
import { TEST_API_KEY, TEST_ORG_ID, TEST_PROJECT_ID } from "./helpers";
import { TEST_API_KEY } from "./helpers";
import {
setupMockFetch,
findFetchCall,
@@ -23,8 +23,6 @@ function webhookMock(extra?: Map<string, { status: number; body: unknown }>) {
function createClient() {
return new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
}
@@ -100,14 +98,6 @@ describe("MemoryClient - createWebhook", () => {
expect(body.eventTypes).toBeUndefined();
});
test("body does not contain projectId", async () => {
const mock = await callCreate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/", "POST")!,
);
expect(body.projectId).toBeUndefined();
});
test("body does not contain webhookId", async () => {
const mock = await callCreate();
const body = getFetchBody(
@@ -173,22 +163,6 @@ describe("MemoryClient - updateWebhook", () => {
expect(body.eventTypes).toBeUndefined();
});
test("body does not contain project_id", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.project_id).toBeUndefined();
});
test("body does not contain projectId", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.projectId).toBeUndefined();
});
test("body does not contain webhookId", async () => {
const mock = await callUpdate();
const body = getFetchBody(
@@ -1,5 +1,10 @@
import { MemoryClient } from "mem0ai";
import type { Memory, MemoryOptions, SearchOptions } from "mem0ai";
import type {
Memory,
AddMemoryOptions,
SearchMemoryOptions,
GetAllMemoryOptions,
} from "mem0ai";
import {
InputValues,
@@ -102,10 +107,6 @@ export const mem0MemoryToMessages = (memories: Memory[]): BaseMessage[] => {
export interface ClientOptions {
apiKey: string;
host?: string;
organizationName?: string;
projectName?: string;
organizationId?: string;
projectId?: string;
}
/**
@@ -117,7 +118,7 @@ export interface Mem0MemoryInput extends BaseChatMemoryInput {
apiKey: string;
humanPrefix?: string;
aiPrefix?: string;
memoryOptions?: MemoryOptions | SearchOptions;
memoryOptions?: AddMemoryOptions | SearchMemoryOptions | GetAllMemoryOptions;
mem0Options?: ClientOptions;
separateMessages?: boolean;
}
@@ -160,7 +161,7 @@ export class Mem0Memory extends BaseChatMemory implements Mem0MemoryInput {
mem0Client: InstanceType<typeof MemoryClient>;
memoryOptions: MemoryOptions | SearchOptions;
memoryOptions: AddMemoryOptions | SearchMemoryOptions | GetAllMemoryOptions;
mem0Options: ClientOptions;
+1 -1
View File
@@ -131,7 +131,7 @@ export class ConfigManager {
userConfig.historyDbPath ||
userConfig.historyStore?.config?.historyDbPath ||
DEFAULT_MEMORY_CONFIG.historyStore?.config?.historyDbPath,
customPrompt: userConfig.customPrompt,
customInstructions: userConfig.customInstructions,
graphStore: {
...DEFAULT_MEMORY_CONFIG.graphStore,
...userConfig.graphStore,
+1 -1
View File
@@ -10,7 +10,7 @@ export interface GraphStoreConfig {
provider: string;
config: Neo4jConfig;
llm?: LLMConfig;
customPrompt?: string;
customInstructions?: string;
}
export function validateNeo4jConfig(config: Neo4jConfig): void {
+7 -7
View File
@@ -136,7 +136,7 @@ export class MemoryGraph {
};
}
async search(query: string, filters: Record<string, any>, limit = 100) {
async search(query: string, filters: Record<string, any>, topK = 100) {
const entityTypeMap = await this._retrieveNodesFromData(query, filters);
const searchOutput = await this._searchGraphDb(
Object.keys(entityTypeMap),
@@ -178,7 +178,7 @@ export class MemoryGraph {
}
}
async getAll(filters: Record<string, any>, limit = 100) {
async getAll(filters: Record<string, any>, topK = 100) {
const session = this.graph.session();
try {
const result = await session.run(
@@ -187,7 +187,7 @@ export class MemoryGraph {
RETURN n.name AS source, type(r) AS relationship, m.name AS target
LIMIT toInteger($limit)
`,
{ user_id: filters["userId"], limit: Math.floor(Number(limit)) },
{ user_id: filters["userId"], limit: Math.floor(Number(topK)) },
);
const finalResults = result.records.map((record) => ({
@@ -253,7 +253,7 @@ export class MemoryGraph {
entityTypeMap: Record<string, string>,
) {
let messages;
if (this.config.graphStore?.customPrompt) {
if (this.config.graphStore?.customInstructions) {
messages = [
{
role: "system",
@@ -263,7 +263,7 @@ export class MemoryGraph {
filters["userId"],
).replace(
"CUSTOM_PROMPT",
`4. ${this.config.graphStore.customPrompt}`,
`4. ${this.config.graphStore.customInstructions}`,
) + "\nPlease provide your response in JSON format.",
},
{ role: "user", content: data },
@@ -307,7 +307,7 @@ export class MemoryGraph {
private async _searchGraphDb(
nodeList: string[],
filters: Record<string, any>,
limit = 100,
topK = 100,
): Promise<SearchOutput[]> {
const resultRelations: SearchOutput[] = [];
const session = this.graph.session();
@@ -344,7 +344,7 @@ export class MemoryGraph {
n_embedding: nEmbedding,
threshold: this.threshold,
user_id: filters["userId"],
limit: Math.floor(Number(limit)),
limit: Math.floor(Number(topK)),
});
resultRelations.push(
+12 -12
View File
@@ -39,7 +39,7 @@ import { captureClientEvent } from "../utils/telemetry";
export class Memory {
private config: MemoryConfig;
private customPrompt: string | undefined;
private customInstructions: string | undefined;
private embedder: Embedder;
private vectorStore!: VectorStore;
private llm: LLM;
@@ -56,7 +56,7 @@ export class Memory {
// Merge and validate config
this.config = ConfigManager.mergeConfig(config);
this.customPrompt = this.config.customPrompt;
this.customInstructions = this.config.customInstructions;
this.embedder = EmbedderFactory.create(
this.config.embedder.provider,
this.config.embedder.config,
@@ -293,11 +293,11 @@ export class Memory {
}
const parsedMessages = messages.map((m) => m.content).join("\n");
const [systemPrompt, userPrompt] = this.customPrompt
const [systemPrompt, userPrompt] = this.customInstructions
? [
this.customPrompt.toLowerCase().includes("json")
? this.customPrompt
: `${this.customPrompt}\n\nYou MUST return a valid JSON object with a 'facts' key containing an array of strings.`,
this.customInstructions.toLowerCase().includes("json")
? this.customInstructions
: `${this.customInstructions}\n\nYou MUST return a valid JSON object with a 'facts' key containing an array of strings.`,
`Input:\n${parsedMessages}`,
]
: getFactRetrievalMessages(parsedMessages);
@@ -478,10 +478,10 @@ export class Memory {
await this._ensureInitialized();
await this._captureEvent("search", {
query_length: query.length,
limit: config.limit,
topK: config.topK,
has_filters: !!config.filters,
});
const { userId, agentId, runId, limit = 100, filters = {} } = config;
const { userId, agentId, runId, topK = 100, filters = {} } = config;
if (userId) filters.userId = userId;
if (agentId) filters.agentId = agentId;
@@ -497,7 +497,7 @@ export class Memory {
const queryEmbedding = await this.embedder.embed(query);
const memories = await this.vectorStore.search(
queryEmbedding,
limit,
topK,
filters,
);
@@ -642,19 +642,19 @@ export class Memory {
async getAll(config: GetAllMemoryOptions): Promise<SearchResult> {
await this._ensureInitialized();
await this._captureEvent("get_all", {
limit: config.limit,
topK: config.topK,
has_user_id: !!config.userId,
has_agent_id: !!config.agentId,
has_run_id: !!config.runId,
});
const { userId, agentId, runId, limit = 100 } = config;
const { userId, agentId, runId, topK = 100 } = config;
const filters: SearchFilters = {};
if (userId) filters.userId = userId;
if (agentId) filters.agentId = agentId;
if (runId) filters.runId = runId;
const [memories] = await this.vectorStore.list(filters, limit);
const [memories] = await this.vectorStore.list(filters, topK);
const excludedKeys = new Set([
"userId",
+2 -2
View File
@@ -14,12 +14,12 @@ export interface AddMemoryOptions extends Entity {
}
export interface SearchMemoryOptions extends Entity {
limit?: number;
topK?: number;
filters?: SearchFilters;
}
export interface GetAllMemoryOptions extends Entity {
limit?: number;
topK?: number;
}
export interface DeleteAllMemoryOptions extends Entity {}
@@ -120,11 +120,11 @@ describe("backward compat: ConfigManager.mergeConfig", () => {
expect(cfg.graphStore!.config.url).toBe("neo4j://custom:7687");
});
it("customPrompt passes through unchanged", () => {
it("customInstructions passes through unchanged", () => {
const cfg = ConfigManager.mergeConfig({
customPrompt: "You are a helpful assistant",
customInstructions: "You are a helpful assistant",
});
expect(cfg.customPrompt).toBe("You are a helpful assistant");
expect(cfg.customInstructions).toBe("You are a helpful assistant");
});
it("version override passes through unchanged", () => {
+4 -4
View File
@@ -60,7 +60,7 @@ export interface GraphStoreConfig {
provider: string;
config: Neo4jConfig;
llm?: LLMConfig;
customPrompt?: string;
customInstructions?: string;
}
export interface MemoryConfig {
@@ -80,7 +80,7 @@ export interface MemoryConfig {
historyStore?: HistoryStoreConfig;
disableHistory?: boolean;
historyDbPath?: string;
customPrompt?: string;
customInstructions?: string;
graphStore?: GraphStoreConfig;
enableGraph?: boolean;
}
@@ -148,7 +148,7 @@ export const MemoryConfigSchema = z.object({
}),
}),
historyDbPath: z.string().optional(),
customPrompt: z.string().optional(),
customInstructions: z.string().optional(),
enableGraph: z.boolean().optional(),
graphStore: z
.object({
@@ -164,7 +164,7 @@ export const MemoryConfigSchema = z.object({
config: z.record(z.string(), z.any()),
})
.optional(),
customPrompt: z.string().optional(),
customInstructions: z.string().optional(),
})
.optional(),
historyStore: z
@@ -331,7 +331,7 @@ export class AzureAISearch implements VectorStore {
*/
async search(
query: number[],
limit: number = 5,
topK: number = 5,
filters?: SearchFilters,
): Promise<VectorStoreResult[]> {
const filterExpression = filters
@@ -341,7 +341,7 @@ export class AzureAISearch implements VectorStore {
const vectorQuery: VectorizedQuery<any> = {
kind: "vector",
vector: query,
kNearestNeighborsCount: limit,
kNearestNeighborsCount: topK,
fields: ["vector"],
};
@@ -355,7 +355,7 @@ export class AzureAISearch implements VectorStore {
filterMode: this.vectorFilterMode as any,
},
filter: filterExpression,
top: limit,
top: topK,
searchFields: ["payload"],
});
} else {
@@ -366,7 +366,7 @@ export class AzureAISearch implements VectorStore {
filterMode: this.vectorFilterMode as any,
},
filter: filterExpression,
top: limit,
top: topK,
});
}
@@ -501,7 +501,7 @@ export class AzureAISearch implements VectorStore {
*/
async list(
filters?: SearchFilters,
limit: number = 100,
topK: number = 100,
): Promise<[VectorStoreResult[], number]> {
const filterExpression = filters
? this.buildFilterExpression(filters)
@@ -509,7 +509,7 @@ export class AzureAISearch implements VectorStore {
const searchResults = await this.searchClient.search("*", {
filter: filterExpression,
top: limit,
top: topK,
});
const results: VectorStoreResult[] = [];
+2 -2
View File
@@ -8,7 +8,7 @@ export interface VectorStore {
): Promise<void>;
search(
query: number[],
limit?: number,
topK?: number,
filters?: SearchFilters,
): Promise<VectorStoreResult[]>;
get(vectorId: string): Promise<VectorStoreResult | null>;
@@ -21,7 +21,7 @@ export interface VectorStore {
deleteCol(): Promise<void>;
list(
filters?: SearchFilters,
limit?: number,
topK?: number,
): Promise<[VectorStoreResult[], number]>;
getUserId(): Promise<string>;
setUserId(userId: string): Promise<void>;
@@ -101,7 +101,7 @@ export class LangchainVectorStore implements VectorStore {
async search(
query: number[],
limit: number = 5,
topK: number = 5,
filters?: SearchFilters, // filters parameter is received but will be ignored
): Promise<VectorStoreResult[]> {
if (this.dimension && query.length !== this.dimension) {
@@ -119,7 +119,7 @@ export class LangchainVectorStore implements VectorStore {
// Call similaritySearchVectorWithScore WITHOUT the filter argument
const results = await this.lcStore.similaritySearchVectorWithScore(
query,
limit,
topK,
// Do not pass lcFilter here
);
@@ -192,7 +192,7 @@ export class LangchainVectorStore implements VectorStore {
async list(
filters?: SearchFilters,
limit: number = 100,
topK: number = 100,
): Promise<[VectorStoreResult[], number]> {
// No standard list method in Langchain core interface.
console.error(
+4 -4
View File
@@ -100,7 +100,7 @@ export class MemoryVectorStore implements VectorStore {
async search(
query: number[],
limit: number = 10,
topK: number = 10,
filters?: SearchFilters,
): Promise<VectorStoreResult[]> {
if (query.length !== this.dimension) {
@@ -136,7 +136,7 @@ export class MemoryVectorStore implements VectorStore {
}
results.sort((a, b) => (b.score || 0) - (a.score || 0));
return results.slice(0, limit);
return results.slice(0, topK);
}
async get(vectorId: string): Promise<VectorStoreResult | null> {
@@ -179,7 +179,7 @@ export class MemoryVectorStore implements VectorStore {
async list(
filters?: SearchFilters,
limit: number = 100,
topK: number = 100,
): Promise<[VectorStoreResult[], number]> {
const rows = this.db.prepare(`SELECT * FROM vectors`).all() as any[];
const results: VectorStoreResult[] = [];
@@ -206,7 +206,7 @@ export class MemoryVectorStore implements VectorStore {
}
}
return [results.slice(0, limit), results.length];
return [results.slice(0, topK), results.length];
}
async getUserId(): Promise<string> {
@@ -164,12 +164,12 @@ export class PGVector implements VectorStore {
async search(
query: number[],
limit: number = 5,
topK: number = 5,
filters?: SearchFilters,
): Promise<VectorStoreResult[]> {
const filterConditions: string[] = [];
const queryVector = `[${query.join(",")}]`; // Format query vector as string with square brackets
const filterValues: any[] = [queryVector, limit];
const filterValues: any[] = [queryVector, topK];
let filterIndex = 3;
if (filters) {
@@ -254,7 +254,7 @@ export class PGVector implements VectorStore {
async list(
filters?: SearchFilters,
limit: number = 100,
topK: number = 100,
): Promise<[VectorStoreResult[], number]> {
const filterConditions: string[] = [];
const filterValues: any[] = [];
@@ -286,7 +286,7 @@ export class PGVector implements VectorStore {
${filterClause}
`;
filterValues.push(limit); // Add limit as the last parameter
filterValues.push(topK); // Add limit as the last parameter
const [listResult, countResult] = await Promise.all([
this.client.query(listQuery, filterValues),
+4 -4
View File
@@ -139,14 +139,14 @@ export class Qdrant implements VectorStore {
async search(
query: number[],
limit: number = 5,
topK: number = 5,
filters?: SearchFilters,
): Promise<VectorStoreResult[]> {
const queryFilter = this.createFilter(filters);
const results = await this.client.search(this.collectionName, {
vector: query,
filter: queryFilter,
limit,
limit: topK,
});
return results.map((hit) => ({
@@ -198,10 +198,10 @@ export class Qdrant implements VectorStore {
async list(
filters?: SearchFilters,
limit: number = 100,
topK: number = 100,
): Promise<[VectorStoreResult[], number]> {
const scrollRequest = {
limit,
limit: topK,
filter: this.createFilter(filters),
with_payload: true,
with_vectors: false,
+5 -5
View File
@@ -359,7 +359,7 @@ export class RedisDB implements VectorStore {
async search(
query: number[],
limit: number = 5,
topK: number = 5,
filters?: SearchFilters,
): Promise<VectorStoreResult[]> {
const snakeFilters = filters ? toSnakeCase(filters) : undefined;
@@ -391,14 +391,14 @@ export class RedisDB implements VectorStore {
DIALECT: 2,
LIMIT: {
from: 0,
size: limit,
size: topK,
},
};
try {
const results = (await this.client.ft.search(
this.indexName,
`${filterExpr} =>[KNN ${limit} @embedding $vec AS __vector_score]`,
`${filterExpr} =>[KNN ${topK} @embedding $vec AS __vector_score]`,
searchOptions,
)) as unknown as RedisSearchResult;
@@ -598,7 +598,7 @@ export class RedisDB implements VectorStore {
async list(
filters?: SearchFilters,
limit: number = 100,
topK: number = 100,
): Promise<[VectorStoreResult[], number]> {
const snakeFilters = filters ? toSnakeCase(filters) : undefined;
const filterExpr = snakeFilters
@@ -613,7 +613,7 @@ export class RedisDB implements VectorStore {
SORTDIR: "DESC",
LIMIT: {
from: 0,
size: limit,
size: topK,
},
};
@@ -231,13 +231,13 @@ See the SQL migration instructions in the code comments.`,
async search(
query: number[],
limit: number = 5,
topK: number = 5,
filters?: SearchFilters,
): Promise<VectorStoreResult[]> {
try {
const rpcQuery: VectorQueryParams = {
query_embedding: query,
match_count: limit,
match_count: topK,
};
if (filters) {
@@ -336,13 +336,13 @@ See the SQL migration instructions in the code comments.`,
async list(
filters?: SearchFilters,
limit: number = 100,
topK: number = 100,
): Promise<[VectorStoreResult[], number]> {
try {
let query = this.client
.from(this.tableName)
.select("*", { count: "exact" })
.limit(limit);
.limit(topK);
if (filters) {
Object.entries(filters).forEach(([key, value]) => {
@@ -76,7 +76,7 @@ export class VectorizeDB implements VectorStore {
async search(
query: number[],
limit: number = 5,
topK: number = 5,
filters?: SearchFilters,
): Promise<VectorStoreResult[]> {
try {
@@ -87,7 +87,7 @@ export class VectorizeDB implements VectorStore {
vector: query,
filter: filters,
returnMetadata: "all",
topK: limit,
topK: topK,
},
);
@@ -197,7 +197,7 @@ export class VectorizeDB implements VectorStore {
async list(
filters?: SearchFilters,
limit: number = 20,
topK: number = 20,
): Promise<[VectorStoreResult[], number]> {
try {
const result = await this.client?.vectorize.indexes.query(
@@ -206,7 +206,7 @@ export class VectorizeDB implements VectorStore {
account_id: this.accountId,
vector: Array(this.dimensions).fill(0), // Dummy vector for listing
filter: filters,
topK: limit,
topK: topK,
returnMetadata: "all",
},
);
@@ -305,13 +305,15 @@ describe("_establishNodesRelationsFromData", () => {
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)
// (only replaced when config.graphStore.customInstructions 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." });
const mg = graph({
customInstructions: "Focus on food relationships only.",
});
await mg._establishNodesRelationsFromData("data", FILTERS, {});
const [messages] = mockGenerateResponse.mock.calls[0];